Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 82 additions & 59 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 (numeric id e.g. 1234, or SDK-1234)'
required: true
type: string
Comment thread
jferrao-itrbl marked this conversation as resolved.

permissions:
contents: write
Expand All @@ -16,71 +20,72 @@ 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: Normalize Jira ticket
id: jira
run: |
raw="${{ github.event.inputs.ticket }}"
ticket="${raw#SDK-}"
ticket="${ticket#sdk-}"
if ! [[ "$ticket" =~ ^[0-9]+$ ]]; then
echo "::error::Invalid ticket. Use the numeric id (e.g., 1234) or SDK-1234."
exit 1
fi
echo "key=SDK-${ticket}" >> "$GITHUB_OUTPUT"

- 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"
echo "::error::No unreleased changes found in $changelog_file. Add release notes to the [Unreleased] section before running this workflow."
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)"
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

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 "\\\"\""
Expand All @@ -89,34 +94,52 @@ 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

- 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: "${{ steps.jira.outputs.key }}: Prepare for Release ${{ github.event.inputs.version }}"
body: |
# Prepare for Release ${{ steps.update_changelog.outputs.new_version }}

# Prepare for Release ${{ github.event.inputs.version }}

## iterable-docs
Replace `0` with the real PR number before merge:
https://github.com/Iterable/iterable-docs/pull/0

## 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, 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 }}"
- [ ] iterable-docs PR linked above (update the URL from the placeholder)
branch: "release/${{ steps.jira.outputs.key }}-${{ github.event.inputs.version }}"
commit-message: "[${{ steps.jira.outputs.key }}]: Prepare for release ${{ github.event.inputs.version }}"
labels: release
Comment thread
jferrao-itrbl marked this conversation as resolved.
delete-branch: true
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

# gh may create the tag on the default branch when the release is named; keep tag off master until Publish.
if git ls-remote --tags origin "refs/tags/$version" | grep -q .; then
echo "Removing premature tag $version (draft release must not point at pre-bump master)."
git push origin ":refs/tags/$version"
fi
127 changes: 127 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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
id: verify_changelog
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: Ensure GitHub release exists
id: release_state
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if ! gh release view "$VERSION" &>/dev/null; then
echo "::error::No GitHub release found for $VERSION. Run Prepare Release before publishing."
exit 1
fi
is_draft=$(gh release view "$VERSION" --json isDraft -q .isDraft)
echo "is_draft=$is_draft" >> "$GITHUB_OUTPUT"
if [ "$is_draft" = "true" ]; then
echo "Release $VERSION is a draft; will publish on GitHub before Maven Central."
else
echo "Release $VERSION is already published; retrying tag alignment and Maven Central upload."
fi

- name: Publish draft GitHub release
if: steps.release_state.outputs.is_draft == 'true'
id: publish_github
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
target="$(git rev-parse HEAD)"
if [[ "$VERSION" == *-* ]]; then
gh release edit "$VERSION" \
--draft=false \
--target "$target" \
--prerelease
else
gh release edit "$VERSION" \
--draft=false \
--target "$target" \
--latest
fi

- name: Point release tag at master HEAD
id: align_tag
run: |
target="$(git rev-parse HEAD)"
# gh release edit does not move an existing tag; align tag to the merged release commit.
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f "$VERSION" "$target"
git push origin "refs/tags/$VERSION" --force

- 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
id: 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: Slack notification
if: steps.central.outcome == 'success'
continue-on-error: true
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 -f -X POST -H 'Content-type: application/json' --data "$payload" "${{ secrets.SLACK_WEBHOOK }}"

- name: Slack failure notification
if: failure() && steps.central.outcome != 'success'
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 -f -X POST -H 'Content-type: application/json' --data "$payload" "${{ secrets.SLACK_WEBHOOK }}"
41 changes: 0 additions & 41 deletions .github/workflows/publish.yml

This file was deleted.

Loading