diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index e2416fc2b..278f2d3df 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -7,6 +7,10 @@ on: description: 'Version number (e.g., 3.6.0 or 1.0.0-beta1)' required: true type: string + ticket: + description: 'Jira ticket number (e.g., 1234 for SDK-1234)' + required: true + type: string permissions: contents: write @@ -16,71 +20,61 @@ jobs: prepare-release: runs-on: ubuntu-latest steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.ITERABLE_SDK_RELEASE_APP_ID }} + private-key: ${{ secrets.ITERABLE_SDK_RELEASE_APP_PRIVATE_KEY }} + - uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + ref: master + token: ${{ steps.app-token.outputs.token }} + + - name: Validate version format + run: | + if ! [[ "${{ github.event.inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "::error::Invalid version format. Use semantic versioning (e.g., 3.6.0 or 3.6.0-beta1)" + exit 1 + fi - name: Update Changelog id: update_changelog run: | + version="${{ github.event.inputs.version }}" changelog_file="CHANGELOG.md" - - # Function to extract content between two patterns, including the first pattern - extract_between() { - awk "/^## \[$1\]/{p=1;print;next} /^## \[/{p=0} p" "$3" - } - - # Get the unreleased content - unreleased_content=$(extract_between "Unreleased" "[0-9]" "$changelog_file") - + + unreleased_content=$(awk '/^## \[Unreleased\]/{p=1;next} /^## \[/{p=0} p' "$changelog_file") + if [ -z "$unreleased_content" ]; then - echo "No unreleased changes found in $changelog_file" - exit 1 - fi - - # Get the current version - current_version=$(grep -oP "^## \[\K[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9]+)?(?=\])" "$changelog_file" | head -n1) - new_version="${{ github.event.inputs.version }}" - - # Validate version format (now includes beta versions) - if ! [[ $new_version =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then - echo "Invalid version format. Please use semantic versioning (e.g., 3.6.0 or 1.0.0-beta1)" + echo "::error::No unreleased changes found in $changelog_file. Add release notes to the [Unreleased] section before running this workflow." exit 1 fi - - echo "new_version=${new_version}" >> $GITHUB_OUTPUT - - # Create temporary file + + notes_file="$RUNNER_TEMP/release-notes.md" + printf '%s' "$unreleased_content" > "$notes_file" + temp_file=$(mktemp) - - # Preserve header and write new content { - # Preserve the header (first 4 lines) head -n 4 "$changelog_file" echo "## [Unreleased]" echo "" - echo "## [$new_version]" - # Remove the "## [Unreleased]" line from unreleased_content if it exists + echo "## [$version]" echo "$unreleased_content" | sed '1{/^## \[Unreleased\]/d}' echo "" - # Get the rest of the file starting from the first version entry sed -n '/^## \[[0-9]/,$p' "$changelog_file" } > "$temp_file" - - # Replace original file mv "$temp_file" "$changelog_file" - name: Update Version Files run: | - # Create temp file for build.gradle updates + version="${{ github.event.inputs.version }}" + + sed -i "s/libraryVersion = '.*'/libraryVersion = '$version'/" iterableapi/build.gradle + sed -i "s/versionName=\".*\"/versionName=\"$version\"/" iterableapi/build.gradle + temp_gradle=$(mktemp) - - # Update iterableapi/build.gradle - libraryVersion and versionName - sed -i "s/libraryVersion = '.*'/libraryVersion = '${{ github.event.inputs.version }}'/" iterableapi/build.gradle - sed -i "s/versionName=\".*\"/versionName=\"${{ github.event.inputs.version }}\"/" iterableapi/build.gradle - - # Update buildConfigField version in defaultConfig section - awk -v version="${{ github.event.inputs.version }}" ' + awk -v version="$version" ' /defaultConfig {/,/}/ { if ($0 ~ /buildConfigField.*ITERABLE_SDK_VERSION/) { print " buildConfigField \"String\", \"ITERABLE_SDK_VERSION\", \"\\\"" version "\\\"\"" @@ -89,34 +83,46 @@ jobs: } { print }' iterableapi/build.gradle > "$temp_gradle" && cat "$temp_gradle" > iterableapi/build.gradle rm "$temp_gradle" - - # Update iterableapi-ui/build.gradle - sed -i "s/libraryVersion = '.*'/libraryVersion = '${{ github.event.inputs.version }}'/" iterableapi-ui/build.gradle - - # Update sample app versions - sed -i "s/implementation 'com.iterable:iterableapi:[^']*'/implementation 'com.iterable:iterableapi:${{ github.event.inputs.version }}'/" sample-apps/inbox-customization/app/build.gradle - sed -i "s/implementation 'com.iterable:iterableapi-ui:[^']*'/implementation 'com.iterable:iterableapi-ui:${{ github.event.inputs.version }}'/" sample-apps/inbox-customization/app/build.gradle + + sed -i "s/libraryVersion = '.*'/libraryVersion = '$version'/" iterableapi-ui/build.gradle + + sed -i "s/implementation 'com.iterable:iterableapi:[^']*'/implementation 'com.iterable:iterableapi:$version'/" sample-apps/inbox-customization/app/build.gradle + sed -i "s/implementation 'com.iterable:iterableapi-ui:[^']*'/implementation 'com.iterable:iterableapi-ui:$version'/" sample-apps/inbox-customization/app/build.gradle - name: Create Pull Request uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 # v5 with: - token: ${{ secrets.GITHUB_TOKEN }} - title: "Prepare for Release ${{ steps.update_changelog.outputs.new_version }}" + token: ${{ steps.app-token.outputs.token }} + title: "SDK-${{ github.event.inputs.ticket }}: Prepare for Release ${{ github.event.inputs.version }}" body: | - # Prepare for Release ${{ steps.update_changelog.outputs.new_version }} - + # Prepare for Release ${{ github.event.inputs.version }} + ## SDK Release Checklist - [ ] CHANGELOG.md updated - [ ] Version numbers updated in build.gradle files: - - [ ] iterableapi/build.gradle (libraryVersion, versionName, and ITERABLE_SDK_VERSION) + - [ ] iterableapi/build.gradle (libraryVersion, versionName, ITERABLE_SDK_VERSION) - [ ] iterableapi-ui/build.gradle (libraryVersion) - [ ] sample-apps/inbox-customization/app/build.gradle (both dependencies) - [ ] README.md reviewed (if needed) - [ ] Sample apps verified - [ ] All tests passing - [ ] Documentation updated (if needed) - - branch: "prepare-for-release-${{ steps.update_changelog.outputs.new_version }}" - commit-message: "Prepare for release ${{ steps.update_changelog.outputs.new_version }}" + branch: "release/SDK-${{ github.event.inputs.ticket }}-${{ github.event.inputs.version }}" + commit-message: "[SDK-${{ github.event.inputs.ticket }}]: Prepare for release ${{ github.event.inputs.version }}" labels: release - delete-branch: true \ No newline at end of file + delete-branch: true + + - name: Create Draft GitHub Release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + version="${{ github.event.inputs.version }}" + + if gh release view "$version" &>/dev/null; then + echo "Draft release $version already exists, skipping." + else + gh release create "$version" \ + --draft \ + --title "$version" \ + --notes-file "$RUNNER_TEMP/release-notes.md" + fi diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 000000000..a5102da6b --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,86 @@ +name: Publish Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 3.6.0)' + required: true + type: string + +env: + VERSION: ${{ github.event.inputs.version }} + +permissions: + contents: write + +jobs: + publish-release: + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.ITERABLE_SDK_RELEASE_APP_ID }} + private-key: ${{ secrets.ITERABLE_SDK_RELEASE_APP_PRIVATE_KEY }} + + - uses: actions/checkout@v4 + with: + ref: master + token: ${{ steps.app-token.outputs.token }} + + - name: Verify release is ready + run: | + if ! grep -qE "^## \[$VERSION\]" CHANGELOG.md; then + echo "::error::CHANGELOG.md has no entry for [$VERSION]. Merge the prepare-release PR to master before running this workflow." + exit 1 + fi + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: "17" + distribution: "temurin" + cache: gradle + + - name: Import GPG Key + run: | + echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 -d | gpg --batch --import + gpg --batch --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --export-secret-keys > ~/.gnupg/secring.gpg + GPG_KEY_ID="${{ secrets.GPG_KEY_ID }}" + echo "SHORT_GPG_KEY_ID=${GPG_KEY_ID: -8}" >> $GITHUB_ENV + + - name: Publish to Sonatype Central + run: ./gradlew clean :iterableapi:assemble :iterableapi-ui:assemble publishAllPublicationsToCentralPortal + env: + ORG_GRADLE_PROJECT_SIGNING_KEY_ID: ${{ env.SHORT_GPG_KEY_ID }} + ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.GPG_PASSPHRASE }} + ORG_GRADLE_PROJECT_SIGNING_SECRET_KEY_RING_FILE: /home/runner/.gnupg/secring.gpg + ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + + - name: Publish draft GitHub release and tag master + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + gh release edit "$VERSION" \ + --draft=false \ + --target "$(git rev-parse HEAD)" \ + --latest + + - name: Slack notification + run: | + release_url="https://github.com/${{ github.repository }}/releases/tag/$VERSION" + payload=$(jq -n \ + --arg text ":android: *Android SDK ${VERSION}* has been released. <${release_url}|View release notes>." \ + '{"text": $text}') + curl -sS -X POST -H 'Content-type: application/json' --data "$payload" "${{ secrets.SLACK_WEBHOOK }}" + + - name: Slack failure notification + if: failure() + run: | + run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}" + payload=$(jq -n \ + --arg text ":alert: Android SDK release ${VERSION} failed (attempt ${{ github.run_attempt }}). Run: ${run_url}" \ + '{"text": $text}') + curl -sS -X POST -H 'Content-type: application/json' --data "$payload" "${{ secrets.SLACK_WEBHOOK }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index f422a9280..000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Publish to Sonatype Central - -on: - push: - branches: - - sonatype-publish # for testing the action - tags: - - "*.*.*" # Match semantic version tags like 3.5.13 - - "*.*.*-*" # Match pre-release tags like 3.6.0-beta1, 3.5.12-stg - workflow_dispatch: # Allow manual trigger - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - java-version: "17" - distribution: "temurin" - cache: gradle - - - name: Import GPG Key and Create Secring - run: | - echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 -d | gpg --batch --import - # Create secring.gpg for Gradle signing plugin compatibility - gpg --batch --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --export-secret-keys > ~/.gnupg/secring.gpg - # Extract last 8 characters for short format - GPG_KEY_ID="${{ secrets.GPG_KEY_ID }}" - echo "SHORT_GPG_KEY_ID=${GPG_KEY_ID: -8}" >> $GITHUB_ENV - - - name: Build and Publish to Sonatype Central - run: ./gradlew clean :iterableapi:assemble :iterableapi-ui:assemble publishAllPublicationsToCentralPortal - env: - ORG_GRADLE_PROJECT_SIGNING_KEY_ID: ${{ env.SHORT_GPG_KEY_ID }} - ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.GPG_PASSPHRASE }} - ORG_GRADLE_PROJECT_SIGNING_SECRET_KEY_RING_FILE: /home/runner/.gnupg/secring.gpg - ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}