Auto-update Native SDKs #113
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: Auto-update Native SDKs | |
| on: | |
| schedule: | |
| # Check for updates daily at 9 AM UTC | |
| - cron: '0 9 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| ios_version: | |
| description: 'Specific iOS SDK version to update to (optional)' | |
| required: false | |
| android_version: | |
| description: 'Specific Android SDK version to update to (optional)' | |
| required: false | |
| jobs: | |
| check-updates: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| actions: read | |
| outputs: | |
| any_updates: ${{ steps.check_releases.outputs.any_updates }} | |
| current_ios: ${{ steps.current_versions.outputs.current_ios }} | |
| current_android: ${{ steps.current_versions.outputs.current_android }} | |
| latest_ios: ${{ steps.check_releases.outputs.latest_ios }} | |
| latest_android: ${{ steps.check_releases.outputs.latest_android }} | |
| ios_needs_update: ${{ steps.check_releases.outputs.ios_needs_update }} | |
| android_needs_update: ${{ steps.check_releases.outputs.android_needs_update }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js for API calls | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Verify GitHub permissions | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "π Verifying GitHub API permissions..." | |
| # Test reading current repo | |
| echo "Testing current repo access..." | |
| gh api repos/${{ github.repository }} --jq '.name' || { | |
| echo "β Cannot access current repository" | |
| exit 1 | |
| } | |
| # Test reading external public releases | |
| echo "Testing external repo access..." | |
| gh api repos/customerio/customerio-ios/releases/latest --jq '.tag_name' || { | |
| echo "β οΈ Cannot access customerio-ios releases - will use fallback" | |
| } | |
| gh api repos/customerio/customerio-android/releases/latest --jq '.tag_name' || { | |
| echo "β οΈ Cannot access customerio-android releases - will use fallback" | |
| } | |
| # Test PR creation permissions (dry run) | |
| echo "Testing PR creation permissions..." | |
| gh api repos/${{ github.repository }}/pulls --method GET --jq 'length' || { | |
| echo "β Cannot access PR endpoint" | |
| exit 1 | |
| } | |
| echo "β GitHub permissions verified successfully" | |
| - name: Get current SDK versions | |
| id: current_versions | |
| run: | | |
| # Get current iOS version from package.json | |
| current_ios=$(grep '"cioNativeiOSSdkVersion":' package.json | sed 's/.*"cioNativeiOSSdkVersion": *"\(.*\)".*/\1/' | sed 's/^= *//') | |
| echo "current_ios=$current_ios" >> $GITHUB_OUTPUT | |
| # Get current Android version from gradle.properties | |
| current_android=$(grep 'customerio.reactnative.cioSDKVersionAndroid=' android/gradle.properties | sed 's/.*customerio.reactnative.cioSDKVersionAndroid=\(.*\)/\1/') | |
| echo "current_android=$current_android" >> $GITHUB_OUTPUT | |
| echo "π Current versions:" | |
| echo "iOS: $current_ios" | |
| echo "Android: $current_android" | |
| - name: Check for latest SDK releases | |
| id: check_releases | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "π Checking for latest SDK releases..." | |
| # Function to get latest release using gh CLI | |
| get_latest_release() { | |
| local repo=$1 | |
| gh api "repos/$repo/releases/latest" --jq '.tag_name' 2>/dev/null | sed 's/^v//' || echo "" | |
| } | |
| CURRENT_IOS="${{ steps.current_versions.outputs.current_ios }}" | |
| CURRENT_ANDROID="${{ steps.current_versions.outputs.current_android }}" | |
| INPUT_IOS="${{ github.event.inputs.ios_version }}" | |
| INPUT_ANDROID="${{ github.event.inputs.android_version }}" | |
| # Get latest versions | |
| LATEST_IOS="${INPUT_IOS:-$(get_latest_release 'customerio/customerio-ios')}" | |
| LATEST_ANDROID="${INPUT_ANDROID:-$(get_latest_release 'customerio/customerio-android')}" | |
| # Check if updates needed | |
| IOS_NEEDS_UPDATE="false" | |
| ANDROID_NEEDS_UPDATE="false" | |
| if [[ -n "$LATEST_IOS" && "$LATEST_IOS" != "$CURRENT_IOS" ]]; then | |
| IOS_NEEDS_UPDATE="true" | |
| fi | |
| if [[ -n "$LATEST_ANDROID" && "$LATEST_ANDROID" != "$CURRENT_ANDROID" ]]; then | |
| ANDROID_NEEDS_UPDATE="true" | |
| fi | |
| # Set outputs | |
| echo "latest_ios=${LATEST_IOS:-$CURRENT_IOS}" >> $GITHUB_OUTPUT | |
| echo "latest_android=${LATEST_ANDROID:-$CURRENT_ANDROID}" >> $GITHUB_OUTPUT | |
| echo "ios_needs_update=$IOS_NEEDS_UPDATE" >> $GITHUB_OUTPUT | |
| echo "android_needs_update=$ANDROID_NEEDS_UPDATE" >> $GITHUB_OUTPUT | |
| echo "any_updates=$([ "$IOS_NEEDS_UPDATE" = "true" ] || [ "$ANDROID_NEEDS_UPDATE" = "true" ] && echo "true" || echo "false")" >> $GITHUB_OUTPUT | |
| echo "π Release check results:" | |
| echo "iOS: $CURRENT_IOS β ${LATEST_IOS:-$CURRENT_IOS} (needs update: $IOS_NEEDS_UPDATE)" | |
| echo "Android: $CURRENT_ANDROID β ${LATEST_ANDROID:-$CURRENT_ANDROID} (needs update: $ANDROID_NEEDS_UPDATE)" | |
| - name: Show check results | |
| run: | | |
| if [[ "${{ steps.check_releases.outputs.any_updates }}" == "true" ]]; then | |
| echo "β Updates found. Proceeding to update job." | |
| else | |
| echo "β No updates needed. Current versions are up to date." | |
| fi | |
| update-sdks: | |
| needs: check-updates | |
| if: needs.check-updates.outputs.any_updates == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| actions: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Check for existing PR | |
| id: check_existing_pr | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "π Checking for existing PRs with same SDK versions..." | |
| # Generate the exact title that would be created and search for existing PRs | |
| IOS_UPDATED="${{ needs.check-updates.outputs.ios_needs_update }}" | |
| ANDROID_UPDATED="${{ needs.check-updates.outputs.android_needs_update }}" | |
| IOS_VERSION="${{ needs.check-updates.outputs.latest_ios }}" | |
| ANDROID_VERSION="${{ needs.check-updates.outputs.latest_android }}" | |
| # Generate the expected title using the same logic as the PR creation step | |
| if [[ "$IOS_UPDATED" == "true" && "$ANDROID_UPDATED" == "true" ]]; then | |
| EXPECTED_TITLE="chore: update Customer.io native SDKs (iOS $IOS_VERSION, Android $ANDROID_VERSION)" | |
| elif [[ "$IOS_UPDATED" == "true" ]]; then | |
| EXPECTED_TITLE="chore: update Customer.io iOS SDK to $IOS_VERSION" | |
| elif [[ "$ANDROID_UPDATED" == "true" ]]; then | |
| EXPECTED_TITLE="chore: update Customer.io Android SDK to $ANDROID_VERSION" | |
| else | |
| EXPECTED_TITLE="chore: update Customer.io native SDKs" | |
| fi | |
| echo "Expected title: $EXPECTED_TITLE" | |
| # Check for existing open PRs with the exact title only | |
| # This prevents false positives from substring matching (e.g., "1.0" matching "1.0.1") | |
| EXISTING_PR=$(gh pr list --state=open --json number,title | \ | |
| jq -r --arg title "$EXPECTED_TITLE" \ | |
| '.[] | select(.title == $title) | .number' | head -1) | |
| if [[ -n "$EXISTING_PR" ]]; then | |
| echo "β οΈ Found existing PR #$EXISTING_PR for the same SDK versions" | |
| echo "existing_pr_number=$EXISTING_PR" >> $GITHUB_OUTPUT | |
| echo "should_skip=true" >> $GITHUB_OUTPUT | |
| # Get PR details | |
| PR_DETAILS=$(gh pr view "$EXISTING_PR" --json title,url) | |
| PR_TITLE=$(echo "$PR_DETAILS" | jq -r '.title') | |
| PR_URL=$(echo "$PR_DETAILS" | jq -r '.url') | |
| echo "Existing PR: $PR_TITLE" | |
| echo "URL: $PR_URL" | |
| else | |
| echo "β No existing PR found for these SDK versions" | |
| echo "should_skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create feature branch | |
| if: steps.check_existing_pr.outputs.should_skip != 'true' | |
| run: | | |
| branch_name="auto-update/native-sdks-$(date +%Y%m%d-%H%M%S)" | |
| echo "branch_name=$branch_name" >> $GITHUB_ENV | |
| git checkout -b "$branch_name" | |
| - name: Update SDK versions | |
| if: steps.check_existing_pr.outputs.should_skip != 'true' | |
| run: | | |
| # Build arguments for version updates | |
| IOS_VERSION="" | |
| ANDROID_VERSION="" | |
| if [[ "${{ needs.check-updates.outputs.ios_needs_update }}" == "true" ]]; then | |
| IOS_VERSION="${{ needs.check-updates.outputs.latest_ios }}" | |
| fi | |
| if [[ "${{ needs.check-updates.outputs.android_needs_update }}" == "true" ]]; then | |
| ANDROID_VERSION="${{ needs.check-updates.outputs.latest_android }}" | |
| fi | |
| # Update iOS version if needed | |
| if [[ -n "$IOS_VERSION" ]]; then | |
| echo "π Updating iOS SDK version to $IOS_VERSION" | |
| # Update the cioNativeiOSSdkVersion in package.json | |
| sed -i "s/\"cioNativeiOSSdkVersion\": *\".*\"/\"cioNativeiOSSdkVersion\": \"= $IOS_VERSION\"/" package.json | |
| echo "β iOS version updated" | |
| fi | |
| # Update Android version if needed | |
| if [[ -n "$ANDROID_VERSION" ]]; then | |
| echo "π Updating Android SDK version to $ANDROID_VERSION" | |
| # Update the customerio.reactnative.cioSDKVersionAndroid in gradle.properties | |
| sed -i "s/customerio.reactnative.cioSDKVersionAndroid=.*/customerio.reactnative.cioSDKVersionAndroid=$ANDROID_VERSION/" android/gradle.properties | |
| echo "β Android version updated" | |
| fi | |
| - name: Install dependencies and run tests | |
| if: steps.check_existing_pr.outputs.should_skip != 'true' | |
| run: | | |
| echo "π¦ Installing dependencies..." | |
| npm ci | |
| echo "π Running TypeScript check..." | |
| npm run typecheck | |
| echo "π Running linting..." | |
| npm run lint | |
| echo "β All checks completed successfully" | |
| - name: Generate PR content from release notes | |
| if: steps.check_existing_pr.outputs.should_skip != 'true' | |
| id: generate_pr_content | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Function to get release notes using gh CLI | |
| get_release_notes() { | |
| local owner=$1 | |
| local repo=$2 | |
| local version=$3 | |
| # Try with 'v' prefix first, then without | |
| for tag in "v$version" "$version"; do | |
| gh api "repos/$owner/$repo/releases/tags/$tag" 2>/dev/null && return 0 | |
| done | |
| return 1 | |
| } | |
| # Function to extract key changes from release notes | |
| extract_key_changes() { | |
| local release_body="$1" | |
| echo "$release_body" | jq -r '.body' | grep -E '^[-*]\s+|^#{1,3}\s+|^[0-9]+\.\s+|^(Added|Fixed|Changed|Updated|Improved|Enhanced):' | head -5 || echo "" | |
| } | |
| # Function to generate PR title | |
| generate_title() { | |
| local ios_updated=$1 | |
| local android_updated=$2 | |
| local ios_version=$3 | |
| local android_version=$4 | |
| if [[ "$ios_updated" == "true" && "$android_updated" == "true" ]]; then | |
| echo "chore: update Customer.io native SDKs (iOS $ios_version, Android $android_version)" | |
| elif [[ "$ios_updated" == "true" ]]; then | |
| echo "chore: update Customer.io iOS SDK to $ios_version" | |
| elif [[ "$android_updated" == "true" ]]; then | |
| echo "chore: update Customer.io Android SDK to $android_version" | |
| else | |
| echo "chore: update Customer.io native SDKs" | |
| fi | |
| } | |
| # Main logic | |
| CURRENT_IOS="${{ needs.check-updates.outputs.current_ios }}" | |
| CURRENT_ANDROID="${{ needs.check-updates.outputs.current_android }}" | |
| NEW_IOS="${{ needs.check-updates.outputs.latest_ios }}" | |
| NEW_ANDROID="${{ needs.check-updates.outputs.latest_android }}" | |
| IOS_UPDATED="${{ needs.check-updates.outputs.ios_needs_update }}" | |
| ANDROID_UPDATED="${{ needs.check-updates.outputs.android_needs_update }}" | |
| # Generate title | |
| TITLE=$(generate_title "$IOS_UPDATED" "$ANDROID_UPDATED" "$NEW_IOS" "$NEW_ANDROID") | |
| # Generate description | |
| DESCRIPTION="## Summary | |
| Automated update of Customer.io native SDK dependencies: | |
| " | |
| if [[ "$IOS_UPDATED" == "true" ]]; then | |
| IOS_RELEASE_URL="https://github.com/customerio/customerio-ios/releases/tag/v$NEW_IOS" | |
| DESCRIPTION+="- **iOS SDK**: $CURRENT_IOS β $NEW_IOS ([Release Notes]($IOS_RELEASE_URL)) | |
| " | |
| # Try to get iOS release notes | |
| if IOS_RELEASE=$(get_release_notes "customerio" "customerio-ios" "$NEW_IOS"); then | |
| IOS_CHANGES=$(extract_key_changes "$IOS_RELEASE") | |
| if [[ -n "$IOS_CHANGES" ]]; then | |
| DESCRIPTION+=" | |
| ## Key Changes | |
| ### iOS SDK $NEW_IOS | |
| $IOS_CHANGES | |
| " | |
| fi | |
| fi | |
| fi | |
| if [[ "$ANDROID_UPDATED" == "true" ]]; then | |
| ANDROID_RELEASE_URL="https://github.com/customerio/customerio-android/releases/tag/v$NEW_ANDROID" | |
| DESCRIPTION+="- **Android SDK**: $CURRENT_ANDROID β $NEW_ANDROID ([Release Notes]($ANDROID_RELEASE_URL)) | |
| " | |
| # Try to get Android release notes | |
| if ANDROID_RELEASE=$(get_release_notes "customerio" "customerio-android" "$NEW_ANDROID"); then | |
| ANDROID_CHANGES=$(extract_key_changes "$ANDROID_RELEASE") | |
| if [[ -n "$ANDROID_CHANGES" ]]; then | |
| if [[ "$IOS_UPDATED" != "true" ]]; then | |
| DESCRIPTION+=" | |
| ## Key Changes | |
| " | |
| fi | |
| DESCRIPTION+=" | |
| ### Android SDK $NEW_ANDROID | |
| $ANDROID_CHANGES | |
| " | |
| fi | |
| fi | |
| fi | |
| DESCRIPTION+=" | |
| ## Testing | |
| - β TypeScript compilation passed | |
| - β ESLint checks passed | |
| - β Dependencies installed successfully | |
| - β React Native package builds successfully | |
| ## Files Updated | |
| " | |
| if [[ "$IOS_UPDATED" == "true" ]]; then | |
| DESCRIPTION+="- \`package.json\` - Updated \`cioNativeiOSSdkVersion\` to \`= $NEW_IOS\` | |
| " | |
| fi | |
| if [[ "$ANDROID_UPDATED" == "true" ]]; then | |
| DESCRIPTION+="- \`android/gradle.properties\` - Updated \`customerio.reactnative.cioSDKVersionAndroid\` to \`$NEW_ANDROID\` | |
| " | |
| fi | |
| DESCRIPTION+=" | |
| ## Migration Notes | |
| This update maintains API compatibility. The React Native wrapper uses the same public API surface, but benefits from bug fixes and improvements in the underlying native SDKs. Please test your integration thoroughly and review the release notes linked above for any specific changes that might affect your implementation. | |
| --- | |
| *π€ This PR was automatically generated and tested*" | |
| # Set outputs | |
| echo "title=$TITLE" >> $GITHUB_OUTPUT | |
| { | |
| echo "description<<EOF" | |
| echo "$DESCRIPTION" | |
| echo "EOF" | |
| } >> $GITHUB_OUTPUT | |
| echo "π Generated PR content:" | |
| echo "Title: $TITLE" | |
| - name: Commit changes | |
| if: steps.check_existing_pr.outputs.should_skip != 'true' | |
| run: | | |
| git config --local user.email "[email protected]" | |
| git config --local user.name "GitHub Action" | |
| git add . | |
| git commit -m "chore: update Customer.io native SDK versions | |
| - iOS: ${{ needs.check-updates.outputs.current_ios }} β ${{ needs.check-updates.outputs.latest_ios }} | |
| - Android: ${{ needs.check-updates.outputs.current_android }} β ${{ needs.check-updates.outputs.latest_android }} | |
| π€ Automated update with compilation verification" | |
| - name: Push branch and create PR | |
| if: steps.check_existing_pr.outputs.should_skip != 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git push origin "$branch_name" | |
| # Create PR | |
| gh pr create \ | |
| --title "${{ steps.generate_pr_content.outputs.title }}" \ | |
| --body "${{ steps.generate_pr_content.outputs.description }}" \ | |
| --base main \ | |
| --head "$branch_name" | |
| echo "β Pull request created successfully!" | |
| - name: Skip PR creation (already exists) | |
| if: steps.check_existing_pr.outputs.should_skip == 'true' | |
| run: | | |
| echo "β οΈ Skipping PR creation - found existing PR #${{ steps.check_existing_pr.outputs.existing_pr_number }} for the same SDK versions" | |
| echo "No new PR will be created to avoid duplicates." | |
| - name: Post workflow summary | |
| run: | | |
| echo "## π Native SDK Update Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ steps.check_existing_pr.outputs.should_skip }}" == "true" ]]; then | |
| echo "### β οΈ Duplicate Prevention" >> $GITHUB_STEP_SUMMARY | |
| echo "- Found existing PR #${{ steps.check_existing_pr.outputs.existing_pr_number }} for the same SDK versions" >> $GITHUB_STEP_SUMMARY | |
| echo "- Skipped creating duplicate PR to avoid spam" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Pending Updates:" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "### Updates Applied:" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ needs.check-updates.outputs.ios_needs_update }}" == "true" ]]; then | |
| echo "- π± **iOS SDK**: ${{ needs.check-updates.outputs.current_ios }} β ${{ needs.check-updates.outputs.latest_ios }}" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [[ "${{ needs.check-updates.outputs.android_needs_update }}" == "true" ]]; then | |
| echo "- π€ **Android SDK**: ${{ needs.check-updates.outputs.current_android }} β ${{ needs.check-updates.outputs.latest_android }}" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ steps.check_existing_pr.outputs.should_skip }}" != "true" ]]; then | |
| echo "### Verification:" >> $GITHUB_STEP_SUMMARY | |
| echo "- β TypeScript compilation passed" >> $GITHUB_STEP_SUMMARY | |
| echo "- β ESLint checks passed" >> $GITHUB_STEP_SUMMARY | |
| echo "- β Dependencies installed successfully" >> $GITHUB_STEP_SUMMARY | |
| echo "- β Sample app builds will be tested after PR creation" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Branch created**: \`$branch_name\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "βΉοΈ Sample app builds will automatically run on the created PR" >> $GITHUB_STEP_SUMMARY | |
| fi |