Release kafka (#381) #16
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: Publish to npm | |
| on: | |
| push: | |
| branches: | |
| - main | |
| # Prevent overlapping releases if multiple PRs merge close together | |
| concurrency: | |
| group: release-main | |
| cancel-in-progress: false | |
| jobs: | |
| detect-changes: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| matrix: ${{ steps.finalize.outputs.matrix }} | |
| has_changes: ${{ steps.finalize.outputs.has_changes }} | |
| bump: ${{ steps.finalize.outputs.bump }} | |
| should_publish: ${{ steps.finalize.outputs.should_publish }} | |
| steps: | |
| - name: Set default outputs | |
| id: defaults | |
| run: | | |
| echo "matrix=[]" >> $GITHUB_OUTPUT | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "bump=patch" >> $GITHUB_OUTPUT | |
| echo "should_publish=false" >> $GITHUB_OUTPUT | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if should skip | |
| id: skip-check | |
| run: | | |
| COMMIT_MSG=$(git log -1 --pretty=%B) | |
| # Only skip our own version bump commits (exact match) | |
| if echo "$COMMIT_MSG" | grep -qE '^chore: bump versions \[skip ci\]$'; then | |
| echo "Skipping: this is a version bump commit" | |
| echo "should_skip=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "should_skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Get PR info from merge commit | |
| id: pr-info | |
| if: steps.skip-check.outputs.should_skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # Use GitHub API to find PR associated with this commit | |
| # This works for both merge commits and squash merges | |
| PR_DATA=$(gh api \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "/repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \ | |
| --jq '.[0] | {number, labels: [.labels[].name]}' 2>/dev/null || echo '{}') | |
| PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number // empty') | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "No PR found for this commit" | |
| echo "should_publish=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "Found PR #$PR_NUMBER" | |
| echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| # Get labels from the API response | |
| LABELS=$(echo "$PR_DATA" | jq -r '.labels | join(" ")') | |
| echo "PR labels: $LABELS" | |
| echo "labels=$LABELS" >> $GITHUB_OUTPUT | |
| # Check if PR has version bump labels | |
| if echo "$LABELS" | grep -qE '\b(patch|minor|major)\b'; then | |
| echo "should_publish=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No version bump label found (patch/minor/major)" | |
| echo "should_publish=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Determine version bump | |
| id: version | |
| if: steps.skip-check.outputs.should_skip != 'true' && steps.pr-info.outputs.should_publish == 'true' | |
| env: | |
| LABELS: ${{ steps.pr-info.outputs.labels }} | |
| run: | | |
| if echo "$LABELS" | grep -qE '\bmajor\b'; then | |
| echo "bump=major" >> $GITHUB_OUTPUT | |
| echo "Version bump: major" | |
| elif echo "$LABELS" | grep -qE '\bminor\b'; then | |
| echo "bump=minor" >> $GITHUB_OUTPUT | |
| echo "Version bump: minor" | |
| else | |
| echo "bump=patch" >> $GITHUB_OUTPUT | |
| echo "Version bump: patch" | |
| fi | |
| - name: Debug commit info | |
| if: steps.skip-check.outputs.should_skip != 'true' && steps.pr-info.outputs.should_publish == 'true' | |
| run: | | |
| echo "Commit SHA: ${{ github.sha }}" | |
| echo "Previous SHA: ${{ github.event.before }}" | |
| echo "PR Number: ${{ steps.pr-info.outputs.pr_number }}" | |
| echo "Files changed:" | |
| git diff --name-only ${{ github.event.before }}..${{ github.sha }} || echo "git diff failed" | |
| - id: filter | |
| if: steps.skip-check.outputs.should_skip != 'true' && steps.pr-info.outputs.should_publish == 'true' | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| base: ${{ github.event.before }} | |
| ref: ${{ github.sha }} | |
| filters: | | |
| pkg_core: | |
| - 'packages/core/lib/**' | |
| - 'packages/core/package.json' | |
| - 'packages/core/README.md' | |
| pkg_amqp: | |
| - 'packages/amqp/lib/**' | |
| - 'packages/amqp/package.json' | |
| - 'packages/amqp/README.md' | |
| pkg_sqs: | |
| - 'packages/sqs/lib/**' | |
| - 'packages/sqs/package.json' | |
| - 'packages/sqs/README.md' | |
| pkg_sns: | |
| - 'packages/sns/lib/**' | |
| - 'packages/sns/package.json' | |
| - 'packages/sns/README.md' | |
| pkg_kafka: | |
| - 'packages/kafka/lib/**' | |
| - 'packages/kafka/package.json' | |
| - 'packages/kafka/README.md' | |
| pkg_gcp_pubsub: | |
| - 'packages/gcp-pubsub/lib/**' | |
| - 'packages/gcp-pubsub/package.json' | |
| - 'packages/gcp-pubsub/README.md' | |
| pkg_gcs_payload_store: | |
| - 'packages/gcs-payload-store/lib/**' | |
| - 'packages/gcs-payload-store/package.json' | |
| - 'packages/gcs-payload-store/README.md' | |
| pkg_s3_payload_store: | |
| - 'packages/s3-payload-store/lib/**' | |
| - 'packages/s3-payload-store/package.json' | |
| - 'packages/s3-payload-store/README.md' | |
| pkg_metrics: | |
| - 'packages/metrics/lib/**' | |
| - 'packages/metrics/package.json' | |
| - 'packages/metrics/README.md' | |
| pkg_outbox_core: | |
| - 'packages/outbox-core/lib/**' | |
| - 'packages/outbox-core/package.json' | |
| - 'packages/outbox-core/README.md' | |
| pkg_redis_message_deduplication_store: | |
| - 'packages/redis-message-deduplication-store/lib/**' | |
| - 'packages/redis-message-deduplication-store/package.json' | |
| - 'packages/redis-message-deduplication-store/README.md' | |
| pkg_schemas: | |
| - 'packages/schemas/lib/**' | |
| - 'packages/schemas/package.json' | |
| - 'packages/schemas/README.md' | |
| - name: Build dynamic matrix | |
| id: build-matrix | |
| if: steps.skip-check.outputs.should_skip != 'true' && steps.pr-info.outputs.should_publish == 'true' | |
| run: | | |
| # Package mapping: filterKey -> name, npmName | |
| declare -A PKG_NAMES=( | |
| ["pkg_core"]="core" | |
| ["pkg_amqp"]="amqp" | |
| ["pkg_sqs"]="sqs" | |
| ["pkg_sns"]="sns" | |
| ["pkg_kafka"]="kafka" | |
| ["pkg_gcp_pubsub"]="gcp-pubsub" | |
| ["pkg_gcs_payload_store"]="gcs-payload-store" | |
| ["pkg_s3_payload_store"]="s3-payload-store" | |
| ["pkg_metrics"]="metrics" | |
| ["pkg_outbox_core"]="outbox-core" | |
| ["pkg_redis_message_deduplication_store"]="redis-message-deduplication-store" | |
| ["pkg_schemas"]="schemas" | |
| ) | |
| CHANGED='${{ steps.filter.outputs.changes }}' | |
| echo "Changed filters: $CHANGED" | |
| # Build matrix JSON array | |
| MATRIX="[" | |
| FIRST=true | |
| for key in "${!PKG_NAMES[@]}"; do | |
| if echo "$CHANGED" | grep -q "\"$key\""; then | |
| NAME="${PKG_NAMES[$key]}" | |
| NPM_NAME="@message-queue-toolkit/${NAME}" | |
| if [ "$FIRST" = true ]; then | |
| FIRST=false | |
| else | |
| MATRIX+="," | |
| fi | |
| MATRIX+="{\"name\":\"${NAME}\",\"npmName\":\"${NPM_NAME}\"}" | |
| fi | |
| done | |
| MATRIX+="]" | |
| echo "Generated matrix: $MATRIX" | |
| echo "matrix=$MATRIX" >> $GITHUB_OUTPUT | |
| if [ "$MATRIX" = "[]" ]; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| fi | |
| # Finalize outputs - consolidates all outputs with proper fallbacks | |
| # Uses environment variables to preserve JSON quoting | |
| - name: Finalize outputs | |
| id: finalize | |
| env: | |
| BUILD_MATRIX: ${{ steps.build-matrix.outputs.matrix }} | |
| BUILD_HAS_CHANGES: ${{ steps.build-matrix.outputs.has_changes }} | |
| DEFAULT_MATRIX: ${{ steps.defaults.outputs.matrix }} | |
| DEFAULT_HAS_CHANGES: ${{ steps.defaults.outputs.has_changes }} | |
| VERSION_BUMP: ${{ steps.version.outputs.bump }} | |
| DEFAULT_BUMP: ${{ steps.defaults.outputs.bump }} | |
| PR_SHOULD_PUBLISH: ${{ steps.pr-info.outputs.should_publish }} | |
| DEFAULT_SHOULD_PUBLISH: ${{ steps.defaults.outputs.should_publish }} | |
| run: | | |
| # Use build-matrix outputs if available, otherwise defaults | |
| if [ -n "$BUILD_MATRIX" ]; then | |
| echo "matrix=$BUILD_MATRIX" >> $GITHUB_OUTPUT | |
| else | |
| echo "matrix=$DEFAULT_MATRIX" >> $GITHUB_OUTPUT | |
| fi | |
| if [ -n "$BUILD_HAS_CHANGES" ]; then | |
| echo "has_changes=$BUILD_HAS_CHANGES" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_changes=$DEFAULT_HAS_CHANGES" >> $GITHUB_OUTPUT | |
| fi | |
| if [ -n "$VERSION_BUMP" ]; then | |
| echo "bump=$VERSION_BUMP" >> $GITHUB_OUTPUT | |
| else | |
| echo "bump=$DEFAULT_BUMP" >> $GITHUB_OUTPUT | |
| fi | |
| if [ -n "$PR_SHOULD_PUBLISH" ]; then | |
| echo "should_publish=$PR_SHOULD_PUBLISH" >> $GITHUB_OUTPUT | |
| else | |
| echo "should_publish=$DEFAULT_SHOULD_PUBLISH" >> $GITHUB_OUTPUT | |
| fi | |
| # Single job that bumps, publishes, and pushes tags/commits at the end | |
| # This avoids the "checkout wrong commit" problem of multi-job workflows | |
| release: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.should_publish == 'true' && needs.detect-changes.outputs.has_changes == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ github.token }} | |
| persist-credentials: true | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.x | |
| registry-url: 'https://registry.npmjs.org' | |
| package-manager-cache: false | |
| - name: Install dependencies | |
| run: npm install --ignore-scripts | |
| - name: Bump versions for changed packages | |
| id: bump | |
| env: | |
| MATRIX: ${{ needs.detect-changes.outputs.matrix }} | |
| BUMP: ${{ needs.detect-changes.outputs.bump }} | |
| run: | | |
| echo "Packages to bump: $MATRIX" | |
| echo "Version bump type: $BUMP" | |
| # Collect version info for later steps | |
| VERSIONS_JSON="{" | |
| FIRST=true | |
| # Parse matrix JSON and bump each package | |
| for PKG_NAME in $(echo "$MATRIX" | jq -r '.[] | .name'); do | |
| echo "Bumping version for $PKG_NAME..." | |
| cd "packages/$PKG_NAME" | |
| OLD_VERSION=$(node -p "require('./package.json').version") | |
| npm version "$BUMP" --no-git-tag-version | |
| NEW_VERSION=$(node -p "require('./package.json').version") | |
| echo " $OLD_VERSION -> $NEW_VERSION" | |
| if [ "$FIRST" = true ]; then | |
| FIRST=false | |
| else | |
| VERSIONS_JSON+="," | |
| fi | |
| VERSIONS_JSON+="\"$PKG_NAME\":\"$NEW_VERSION\"" | |
| cd ../.. | |
| done | |
| VERSIONS_JSON+="}" | |
| echo "versions=$VERSIONS_JSON" >> $GITHUB_OUTPUT | |
| - name: Build all packages | |
| env: | |
| MATRIX: ${{ needs.detect-changes.outputs.matrix }} | |
| run: | | |
| for NPM_NAME in $(echo "$MATRIX" | jq -r '.[] | .npmName'); do | |
| echo "Building $NPM_NAME..." | |
| npm run build -- --filter="$NPM_NAME" | |
| done | |
| - name: Publish packages to npm | |
| id: publish | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| MATRIX: ${{ needs.detect-changes.outputs.matrix }} | |
| run: | | |
| PUBLISHED="" | |
| SKIPPED="" | |
| for PKG_NAME in $(echo "$MATRIX" | jq -r '.[] | .name'); do | |
| cd "packages/$PKG_NAME" | |
| # Read actual name and version from package.json for accuracy | |
| NPM_NAME=$(node -p "require('./package.json').name") | |
| VERSION=$(node -p "require('./package.json').version") | |
| # Check if this version is already published (idempotent) | |
| if npm view "$NPM_NAME@$VERSION" version >/dev/null 2>&1; then | |
| echo "Skipping $NPM_NAME@$VERSION - already published" | |
| SKIPPED+="$NPM_NAME@$VERSION " | |
| else | |
| echo "Publishing $NPM_NAME@$VERSION..." | |
| npm publish --provenance --access public | |
| PUBLISHED+="$NPM_NAME@$VERSION " | |
| fi | |
| cd ../.. | |
| done | |
| echo "published=$PUBLISHED" >> $GITHUB_OUTPUT | |
| echo "skipped=$SKIPPED" >> $GITHUB_OUTPUT | |
| - name: Commit version bumps | |
| run: | | |
| git add packages/*/package.json | |
| if git diff --staged --quiet; then | |
| echo "No version changes to commit" | |
| else | |
| git commit -m "chore: bump versions [skip ci]" | |
| fi | |
| - name: Create tags for published packages | |
| env: | |
| MATRIX: ${{ needs.detect-changes.outputs.matrix }} | |
| run: | | |
| for PKG_NAME in $(echo "$MATRIX" | jq -r '.[] | .name'); do | |
| # Read actual name from package.json for accurate tag | |
| NPM_NAME=$(node -p "require('./packages/$PKG_NAME/package.json').name") | |
| VERSION=$(node -p "require('./packages/$PKG_NAME/package.json').version") | |
| TAG="${NPM_NAME}@${VERSION}" | |
| # Check if tag already exists (could indicate re-run or version issue) | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "Warning: Tag already exists: $TAG (skipping)" | |
| else | |
| echo "Creating tag: $TAG" | |
| git tag "$TAG" | |
| fi | |
| done | |
| - name: Push commits and tags | |
| run: | | |
| git push | |
| git push --tags | |
| - name: Summary | |
| env: | |
| PUBLISHED: ${{ steps.publish.outputs.published }} | |
| SKIPPED: ${{ steps.publish.outputs.skipped }} | |
| run: | | |
| echo "## Release Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -n "$PUBLISHED" ]; then | |
| echo "### Published packages" >> $GITHUB_STEP_SUMMARY | |
| for PKG in $PUBLISHED; do | |
| # URL-encode the @ symbol for npm links | |
| NPM_URL_NAME=$(echo "$PKG" | sed 's/@/%40/g' | sed 's/@/%40/') | |
| echo "- [$PKG](https://www.npmjs.com/package/${NPM_URL_NAME%@*})" >> $GITHUB_STEP_SUMMARY | |
| done | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ -n "$SKIPPED" ]; then | |
| echo "### Skipped (already published)" >> $GITHUB_STEP_SUMMARY | |
| for PKG in $SKIPPED; do | |
| echo "- $PKG" >> $GITHUB_STEP_SUMMARY | |
| done | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ -z "$PUBLISHED" ] && [ -z "$SKIPPED" ]; then | |
| echo "No packages were published or skipped." >> $GITHUB_STEP_SUMMARY | |
| fi |