|
| 1 | +name: Create Release PR |
| 2 | + |
| 3 | +on: |
| 4 | + workflow_dispatch: |
| 5 | + inputs: |
| 6 | + release-type: |
| 7 | + description: "Release type" |
| 8 | + required: true |
| 9 | + default: "stable" |
| 10 | + type: choice |
| 11 | + options: |
| 12 | + - Current |
| 13 | + - Beta |
| 14 | + - Alpha |
| 15 | + |
| 16 | +jobs: |
| 17 | + bump-version: |
| 18 | + runs-on: ubuntu-latest |
| 19 | + |
| 20 | + env: |
| 21 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 22 | + |
| 23 | + steps: |
| 24 | + - name: Checkout repository |
| 25 | + uses: actions/checkout@v5 |
| 26 | + with: |
| 27 | + fetch-depth: 0 |
| 28 | + fetch-tags: true |
| 29 | + |
| 30 | + - name: Set up Bun |
| 31 | + uses: oven-sh/setup-bun@v2 |
| 32 | + with: |
| 33 | + bun-version: latest |
| 34 | + |
| 35 | + - name: Get current SDK version |
| 36 | + id: current_version |
| 37 | + run: | |
| 38 | + CURRENT_VERSION=$(bun -e "console.log(require('./package.json').version)") |
| 39 | + echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT |
| 40 | +
|
| 41 | + - name: Get last release commit |
| 42 | + id: last_commit |
| 43 | + run: | |
| 44 | + LAST_RELEASE_DATE=$(git show -s --format=%cI "${{ steps.current_version.outputs.current }}") |
| 45 | + echo "date=$LAST_RELEASE_DATE" >> $GITHUB_OUTPUT |
| 46 | +
|
| 47 | + - name: Get merged PRs since last release |
| 48 | + id: get_prs |
| 49 | + uses: actions/github-script@v8 |
| 50 | + with: |
| 51 | + script: | |
| 52 | + const lastReleaseDate = '${{ steps.last_commit.outputs.date }}'; |
| 53 | +
|
| 54 | + // Get merged PRs |
| 55 | + const { data: prs } = await github.rest.pulls.list({ |
| 56 | + owner: context.repo.owner, |
| 57 | + repo: context.repo.repo, |
| 58 | + state: 'closed', |
| 59 | + base: 'main', |
| 60 | + per_page: 100 |
| 61 | + }); |
| 62 | +
|
| 63 | + // Filter and process PRs |
| 64 | + const mergedPrs = prs |
| 65 | + .filter(pr => pr.merged_at && new Date(pr.merged_at) > new Date(lastReleaseDate)) |
| 66 | + .map(pr => ({ |
| 67 | + number: pr.number, |
| 68 | + title: pr.title, |
| 69 | + })); |
| 70 | + core.setOutput('prs', JSON.stringify(mergedPrs)); |
| 71 | +
|
| 72 | + const hasFeatures = mergedPrs.some(pr => /^feat/i.test(pr.title)); |
| 73 | + core.setOutput('isFeature', hasFeatures); |
| 74 | +
|
| 75 | + - name: Calculate new version |
| 76 | + id: new_version |
| 77 | + run: | |
| 78 | + CURRENT="${{ steps.current_version.outputs.current }}" |
| 79 | + RELEASE_TYPE="${{ inputs.release-type }}" |
| 80 | + IS_FEATURE='${{ steps.get_prs.outputs.isFeature }}' |
| 81 | +
|
| 82 | + # Extract base version and determine current pre-release type |
| 83 | + if [[ "$CURRENT" =~ -alpha\. ]]; then |
| 84 | + BASE_VERSION="${CURRENT%-alpha.*}" |
| 85 | + PRERELEASE_NUM="${CURRENT##*-alpha.}" |
| 86 | + PRERELEASE_TYPE="alpha" |
| 87 | + elif [[ "$CURRENT" =~ -beta\. ]]; then |
| 88 | + BASE_VERSION="${CURRENT%-beta.*}" |
| 89 | + PRERELEASE_NUM="${CURRENT##*-beta.}" |
| 90 | + PRERELEASE_TYPE="beta" |
| 91 | + else |
| 92 | + BASE_VERSION="$CURRENT" |
| 93 | + PRERELEASE_TYPE="stable" |
| 94 | + fi |
| 95 | +
|
| 96 | + # Helper function to bump version |
| 97 | + bump_version() { |
| 98 | + local base=$1 |
| 99 | + local is_feature=$2 |
| 100 | + local major=${base:0:2} |
| 101 | + local minor=${base:2:2} |
| 102 | + local patch=${base:4:2} |
| 103 | +
|
| 104 | + if [[ "$is_feature" == "true" ]]; then |
| 105 | + minor=$(printf "%02d" $((10#$minor + 1))) |
| 106 | + patch="00" |
| 107 | + else |
| 108 | + patch=$(printf "%02d" $((10#$patch + 1))) |
| 109 | + fi |
| 110 | +
|
| 111 | + echo "${major}${minor}${patch}" |
| 112 | + } |
| 113 | +
|
| 114 | + # Determine new version based on current and desired release types |
| 115 | + if [[ "$RELEASE_TYPE" == "Alpha" ]]; then |
| 116 | + if [[ "$PRERELEASE_TYPE" == "alpha" ]]; then |
| 117 | + # Increment alpha number |
| 118 | + NEW_VERSION="$BASE_VERSION-alpha.$((PRERELEASE_NUM + 1))" |
| 119 | + else |
| 120 | + # New alpha release from stable or beta |
| 121 | + NEW_VERSION="$(bump_version "$BASE_VERSION" "$IS_FEATURE")-alpha.1" |
| 122 | + fi |
| 123 | + elif [[ "$RELEASE_TYPE" == "Beta" ]]; then |
| 124 | + if [[ "$PRERELEASE_TYPE" == "beta" ]]; then |
| 125 | + # Increment beta number |
| 126 | + NEW_VERSION="$BASE_VERSION-beta.$((PRERELEASE_NUM + 1))" |
| 127 | + elif [[ "$PRERELEASE_TYPE" == "alpha" ]]; then |
| 128 | + # Promote alpha to beta |
| 129 | + NEW_VERSION="$BASE_VERSION-beta.1" |
| 130 | + else |
| 131 | + # New beta release from stable |
| 132 | + NEW_VERSION="$(bump_version "$BASE_VERSION" "$IS_FEATURE")-beta.1" |
| 133 | + fi |
| 134 | + else |
| 135 | + # Release type is Current (stable) |
| 136 | + if [[ "$PRERELEASE_TYPE" != "stable" ]]; then |
| 137 | + # Promote pre-release to stable |
| 138 | + NEW_VERSION="$BASE_VERSION" |
| 139 | + else |
| 140 | + # Bump stable version |
| 141 | + NEW_VERSION="$(bump_version "$CURRENT" "$IS_FEATURE")" |
| 142 | + fi |
| 143 | + fi |
| 144 | +
|
| 145 | + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT |
| 146 | +
|
| 147 | + - name: Create release branch |
| 148 | + run: | |
| 149 | + git checkout -b rel/${{ steps.new_version.outputs.version }} |
| 150 | + git push -u origin rel/${{ steps.new_version.outputs.version }} |
| 151 | +
|
| 152 | + - name: Create temp branch |
| 153 | + if: inputs.release-type == 'Alpha' || inputs.release-type == 'Beta' |
| 154 | + run: | |
| 155 | + git checkout -b release-${{ steps.new_version.outputs.version }} |
| 156 | + git push -u origin release-${{ steps.new_version.outputs.version }} |
| 157 | +
|
| 158 | + # Cordova specific steps |
| 159 | + - name: Update sdk version |
| 160 | + run: | |
| 161 | + NEW_VERSION="${{ steps.new_version.outputs.version }}" |
| 162 | + git config user.name "github-actions[bot]" |
| 163 | + git config user.email "github-actions[bot]@users.noreply.github.com" |
| 164 | +
|
| 165 | + # Convert version format for OneSignal wrapper (e.g., 5.2.15 -> 050215) |
| 166 | + # For pre-releases, extract base version first (e.g., 5.2.15-alpha.1 -> 5.2.15) |
| 167 | + BASE_VERSION=$(echo "$NEW_VERSION" | sed 's/-[a-z].*//') |
| 168 | + WRAPPER_VERSION=$(echo "$BASE_VERSION" | sed 's/\.//g' | awk '{printf "%06d", $0}') |
| 169 | +
|
| 170 | + # Update package.json version |
| 171 | + npm pkg set version="$NEW_VERSION" |
| 172 | +
|
| 173 | + # Update plugin.xml cordova plugin version (target <plugin> element specifically) |
| 174 | + sed -i 's|<plugin \(xmlns="[^"]*" xmlns:android="[^"]*" id="[^"]*"\) version="[^"]*"|<plugin \1 version="'"$NEW_VERSION"'"|' plugin.xml |
| 175 | +
|
| 176 | + # Update OneSignalPush.java wrapper version |
| 177 | + sed -i "s/OneSignalWrapper\.setSdkVersion(\"[^\"]*\")/OneSignalWrapper.setSdkVersion(\"$WRAPPER_VERSION\")/g" src/android/com/onesignal/cordova/OneSignalPush.java |
| 178 | +
|
| 179 | + # Update OneSignalPush.m wrapper version |
| 180 | + sed -i "s/OneSignalWrapper\.sdkVersion = @\"[^\"]*\"/OneSignalWrapper.sdkVersion = @\"$WRAPPER_VERSION\"/g" src/ios/OneSignalPush.m |
| 181 | +
|
| 182 | + git add package.json plugin.xml src/android/com/onesignal/cordova/OneSignalPush.java src/ios/OneSignalPush.m |
| 183 | +
|
| 184 | + git commit -m "Release $NEW_VERSION" |
| 185 | + git push |
| 186 | +
|
| 187 | + - name: Check native SDK version changes |
| 188 | + id: native_deps |
| 189 | + run: | |
| 190 | + # Get the current plugin.xml |
| 191 | + CURRENT_PLUGIN=$(cat plugin.xml) |
| 192 | +
|
| 193 | + # Extract current Android SDK version |
| 194 | + ANDROID_VERSION=$(echo "$CURRENT_PLUGIN" | grep -oP 'com\.onesignal:OneSignal:\K[0-9.]+' | head -1) |
| 195 | +
|
| 196 | + # Extract current iOS SDK version |
| 197 | + IOS_VERSION=$(echo "$CURRENT_PLUGIN" | grep -oP 'OneSignalXCFramework.*spec="\K[0-9.]+' | head -1) |
| 198 | +
|
| 199 | + # Get previous plugin.xml from HEAD~1 (before the commit we just made) |
| 200 | + PREVIOUS_PLUGIN=$(git show HEAD~1:plugin.xml) |
| 201 | +
|
| 202 | + # Extract previous Android SDK version |
| 203 | + PREVIOUS_ANDROID=$(echo "$PREVIOUS_PLUGIN" | grep -oP 'com\.onesignal:OneSignal:\K[0-9.]+' | head -1) |
| 204 | +
|
| 205 | + # Extract previous iOS SDK version |
| 206 | + PREVIOUS_IOS=$(echo "$PREVIOUS_PLUGIN" | grep -oP 'OneSignalXCFramework.*spec="\K[0-9.]+' | head -1) |
| 207 | +
|
| 208 | + # Build output for native dependency changes |
| 209 | + NATIVE_UPDATES="" |
| 210 | + if [[ "$ANDROID_VERSION" != "$PREVIOUS_ANDROID" && ! -z "$PREVIOUS_ANDROID" ]]; then |
| 211 | + printf -v NATIVE_UPDATES '%sANDROID_UPDATE=true\nANDROID_FROM=%s\nANDROID_TO=%s\n' "$NATIVE_UPDATES" "$PREVIOUS_ANDROID" "$ANDROID_VERSION" |
| 212 | + fi |
| 213 | +
|
| 214 | + if [[ "$IOS_VERSION" != "$PREVIOUS_IOS" && ! -z "$PREVIOUS_IOS" ]]; then |
| 215 | + printf -v NATIVE_UPDATES '%sIOS_UPDATE=true\nIOS_FROM=%s\nIOS_TO=%s\n' "$NATIVE_UPDATES" "$PREVIOUS_IOS" "$IOS_VERSION" |
| 216 | + fi |
| 217 | +
|
| 218 | + # Output the variables |
| 219 | + echo "$NATIVE_UPDATES" >> $GITHUB_OUTPUT |
| 220 | +
|
| 221 | + - name: Generate release notes |
| 222 | + id: release_notes |
| 223 | + uses: actions/github-script@v8 |
| 224 | + with: |
| 225 | + script: | |
| 226 | + // Trim whitespace from PR titles |
| 227 | + const prs = JSON.parse('${{ steps.get_prs.outputs.prs }}').map(pr => ({ |
| 228 | + ...pr, |
| 229 | + title: pr.title.trim() |
| 230 | + })); |
| 231 | +
|
| 232 | + // Categorize PRs |
| 233 | + const features = prs.filter(pr => /^feat/i.test(pr.title)); |
| 234 | + const fixes = prs.filter(pr => /^fix/i.test(pr.title)); |
| 235 | + const improvements = prs.filter(pr => /^(perf|refactor|chore)/i.test(pr.title)); |
| 236 | +
|
| 237 | + // Helper function to build section |
| 238 | + const buildSection = (title, prs) => { |
| 239 | + if (prs.length === 0) return ''; |
| 240 | + let section = `### ${title}\n\n`; |
| 241 | + prs.forEach(pr => { |
| 242 | + section += `- ${pr.title} (#${pr.number})\n`; |
| 243 | + }); |
| 244 | + return section + '\n'; |
| 245 | + }; |
| 246 | +
|
| 247 | + let releaseNotes = `Channels: ${{ inputs.release-type }}\n\n`; |
| 248 | + releaseNotes += buildSection('🚀 New Features', features); |
| 249 | + releaseNotes += buildSection('🐛 Bug Fixes', fixes); |
| 250 | + releaseNotes += buildSection('✨ Improvements', improvements); |
| 251 | +
|
| 252 | + // Check for native dependency changes |
| 253 | + const hasAndroidUpdate = '${{ steps.native_deps.outputs.ANDROID_UPDATE }}' === 'true'; |
| 254 | + const hasIosUpdate = '${{ steps.native_deps.outputs.IOS_UPDATE }}' === 'true'; |
| 255 | +
|
| 256 | + if (hasAndroidUpdate || hasIosUpdate) { |
| 257 | + releaseNotes += '\n### 🛠️ Native Dependency Updates\n\n'; |
| 258 | + if (hasAndroidUpdate) { |
| 259 | + releaseNotes += `- Update Android SDK from ${{ steps.native_deps.outputs.ANDROID_FROM }} to ${{ steps.native_deps.outputs.ANDROID_TO }}\n`; |
| 260 | + releaseNotes += `- See [release notes](https://github.com/OneSignal/OneSignal-Android-SDK/releases) for full details\n`; |
| 261 | + } |
| 262 | + if (hasIosUpdate) { |
| 263 | + releaseNotes += `- Update iOS SDK from ${{ steps.native_deps.outputs.IOS_FROM }} to ${{ steps.native_deps.outputs.IOS_TO }}\n`; |
| 264 | + releaseNotes += `- See [release notes](https://github.com/OneSignal/OneSignal-iOS-SDK/releases) for full details\n`; |
| 265 | + } |
| 266 | + releaseNotes += '\n'; |
| 267 | + } |
| 268 | +
|
| 269 | + core.setOutput('notes', releaseNotes); |
| 270 | +
|
| 271 | + - name: Create release PR |
| 272 | + run: | |
| 273 | + NEW_VERSION="${{ steps.new_version.outputs.version }}" |
| 274 | +
|
| 275 | + # Write release notes to file to avoid shell interpretation |
| 276 | + cat > release_notes.md << 'EOF' |
| 277 | + ${{ steps.release_notes.outputs.notes }} |
| 278 | + EOF |
| 279 | +
|
| 280 | + gh pr create \ |
| 281 | + --title "Release $NEW_VERSION" \ |
| 282 | + --body-file release_notes.md \ |
| 283 | + --base main \ |
| 284 | + --reviewer fadi-george,sherwinski,jkasten2 |
0 commit comments