From 71710aa88633a289844a82c5db19ee29b4594010 Mon Sep 17 00:00:00 2001 From: Joao Dordio Date: Fri, 7 Aug 2026 18:28:18 +0100 Subject: [PATCH] chore: automate RN release with prepare and publish workflows Aligns the RN release process with the iOS and Android automations. - Add prepare-release.yml (workflow_dispatch) that bumps package.json, regenerates src/itblBuildInfo.ts, optionally bumps the pinned Iterable iOS and Android SDK versions in Iterable-React-Native-SDK.podspec and android/build.gradle, promotes the CHANGELOG.md ## [Unreleased] block to ## X.Y.Z, and opens a release-labeled PR via peter-evans. - Add release.yml (workflow_dispatch) that verifies version consistency, extracts the changelog section, tags the target branch, creates the GitHub Release, and publishes to npm with OIDC provenance. - Add a ## [Unreleased] marker to CHANGELOG.md so contributors accumulate entries there and the workflow can promote them at release time. Older entries left untouched. The existing Validate Release PR workflow still enforces the docs PR link, changelog entry, and itblBuildInfo.ts version consistency, so the generated PRs are validated by the same rules as manual ones. --- .github/workflows/prepare-release.yml | 204 ++++++++++++++++++++++++++ .github/workflows/release.yml | 136 +++++++++++++++++ CHANGELOG.md | 2 + 3 files changed, 342 insertions(+) create mode 100644 .github/workflows/prepare-release.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 000000000..12fd3cb59 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,204 @@ +name: Prepare Release + +on: + workflow_dispatch: + inputs: + version: + description: 'RN SDK version (e.g., 3.2.0 or 3.2.0-beta1)' + required: true + type: string + ios_sdk_version: + description: 'Optional: Iterable iOS SDK pin (e.g., 6.6.8). Leave blank to keep current.' + required: false + type: string + android_sdk_version: + description: 'Optional: Iterable Android SDK pin (e.g., 3.6.3). Leave blank to keep current.' + required: false + type: string + jira_ticket: + description: 'Optional: SDK Jira ticket number (e.g., 1234) for branch/PR naming.' + required: false + type: string + docs_pr_url: + description: 'Optional: iterable-docs PR URL to pre-fill in the release PR body.' + required: false + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + prepare-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Validate version format + run: | + version="${{ github.event.inputs.version }}" + if ! [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "::error::Invalid RN version '$version'. Use semver (e.g., 3.2.0 or 3.2.0-beta1)." + exit 1 + fi + if [ -n "${{ github.event.inputs.ios_sdk_version }}" ] && ! [[ "${{ github.event.inputs.ios_sdk_version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "::error::Invalid ios_sdk_version '${{ github.event.inputs.ios_sdk_version }}'." + exit 1 + fi + if [ -n "${{ github.event.inputs.android_sdk_version }}" ] && ! [[ "${{ github.event.inputs.android_sdk_version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "::error::Invalid android_sdk_version '${{ github.event.inputs.android_sdk_version }}'." + exit 1 + fi + + - name: Promote Unreleased to new version in CHANGELOG.md + id: update_changelog + run: | + version="${{ github.event.inputs.version }}" + changelog_file="CHANGELOG.md" + + if ! grep -qE '^## \[Unreleased\]' "$changelog_file"; then + echo "::error::CHANGELOG.md is missing a '## [Unreleased]' marker. Add it above the newest version header." + exit 1 + fi + + # Extract everything from '## [Unreleased]' up to (but not including) the next version header. + # Version headers may be either '## [X.Y.Z]' (bracketed) or '## X.Y.Z' (RN legacy format). + unreleased_body=$(awk ' + /^## \[Unreleased\]/ { capturing=1; next } + /^## (\[)?[0-9]+\.[0-9]+\.[0-9]+/ { if (capturing) { exit } } + capturing { print } + ' "$changelog_file") + + # Trim leading/trailing blank lines from the extracted body. + unreleased_body=$(printf '%s\n' "$unreleased_body" | awk 'NF{found=1} found' | awk '{lines[NR]=$0} END{for(i=NR;i>=1;i--) if(lines[i]!=""){last=i;break} for(i=1;i<=last;i++) print lines[i]}') + + if [ -z "$unreleased_body" ]; then + echo "::error::No unreleased entries found under '## [Unreleased]'. Add release notes before running this workflow." + exit 1 + fi + + # Rebuild the file: preserve everything before '## [Unreleased]', + # write a fresh empty Unreleased block, write the new version block, + # then append everything from the first prior version header onward. + header=$(awk '/^## \[Unreleased\]/ { exit } { print }' "$changelog_file") + rest=$(awk ' + /^## \[Unreleased\]/ { seen_unreleased=1; next } + seen_unreleased && /^## (\[)?[0-9]+\.[0-9]+\.[0-9]+/ { printing=1 } + printing { print } + ' "$changelog_file") + + { + if [ -n "$header" ]; then + printf '%s\n' "$header" + fi + echo "## [Unreleased]" + echo "" + echo "## $version" + echo "" + printf '%s\n' "$unreleased_body" + echo "" + if [ -n "$rest" ]; then + printf '%s\n' "$rest" + fi + } > "$changelog_file.new" + + mv "$changelog_file.new" "$changelog_file" + echo "new_version=$version" >> $GITHUB_OUTPUT + + - name: Bump package.json version + run: npm --no-git-tag-version --allow-same-version version "${{ github.event.inputs.version }}" + + - name: Regenerate src/itblBuildInfo.ts + run: node scripts/autoCreatePackageInfo.js + + - name: Update iOS SDK pin (podspec) + if: ${{ github.event.inputs.ios_sdk_version != '' }} + run: | + ios_ver="${{ github.event.inputs.ios_sdk_version }}" + sed -i -E "s|(s\.dependency[[:space:]]+\"Iterable-iOS-SDK\",[[:space:]]+)\"[^\"]+\"|\1\"$ios_ver\"|" Iterable-React-Native-SDK.podspec + if ! grep -qE "s\.dependency[[:space:]]+\"Iterable-iOS-SDK\",[[:space:]]+\"$ios_ver\"" Iterable-React-Native-SDK.podspec; then + echo "::error::Failed to update Iterable-iOS-SDK dependency in Iterable-React-Native-SDK.podspec." + exit 1 + fi + + - name: Update Android SDK pin (build.gradle) + if: ${{ github.event.inputs.android_sdk_version != '' }} + run: | + android_ver="${{ github.event.inputs.android_sdk_version }}" + sed -i -E "s|(api[[:space:]]+\"com\.iterable:iterableapi:)[^\"]+\"|\1$android_ver\"|" android/build.gradle + if ! grep -qE "api[[:space:]]+\"com\.iterable:iterableapi:$android_ver\"" android/build.gradle; then + echo "::error::Failed to update com.iterable:iterableapi dependency in android/build.gradle." + exit 1 + fi + + - name: Compose branch, title, and body + id: meta + run: | + version="${{ github.event.inputs.version }}" + jira="${{ github.event.inputs.jira_ticket }}" + ios_ver="${{ github.event.inputs.ios_sdk_version }}" + android_ver="${{ github.event.inputs.android_sdk_version }}" + docs_pr="${{ github.event.inputs.docs_pr_url }}" + + if [ -n "$jira" ]; then + branch="release/SDK-$jira-prepare-for-release-$version" + title="SDK-$jira: Prepare for Release $version" + commit="[SDK-$jira]: Prepare for release $version" + else + branch="prepare-for-release-$version" + title="Prepare for Release $version" + commit="Prepare for release $version" + fi + + { + echo "branch=$branch" + echo "title=$title" + echo "commit=$commit" + } >> $GITHUB_OUTPUT + + { + echo 'body<> $GITHUB_OUTPUT + + - name: Create Pull Request + uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 # v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + title: ${{ steps.meta.outputs.title }} + body: ${{ steps.meta.outputs.body }} + branch: ${{ steps.meta.outputs.branch }} + commit-message: ${{ steps.meta.outputs.commit }} + labels: release + delete-branch: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..1d5427fae --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,136 @@ +name: React Native SDK Release +run-name: ${{ github.actor }} is releasing RN SDK ${{ github.event.inputs.version }} + +on: + workflow_dispatch: + inputs: + version: + description: 'RN SDK version to release (must match package.json on the target branch, e.g., 3.2.0)' + required: true + type: string + branch: + description: 'Branch to release from (defaults to master)' + required: true + default: 'master' + type: string + set_prerelease: + description: 'Mark GitHub release as prerelease' + required: false + default: false + type: boolean + +permissions: + contents: write + id-token: write + +jobs: + release: + environment: npm + runs-on: ubuntu-latest + env: + VERSION: ${{ github.event.inputs.version }} + BRANCH: ${{ github.event.inputs.branch }} + SET_PRERELEASE: ${{ github.event.inputs.set_prerelease }} + steps: + - name: Validate version format + run: | + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "::error::Invalid version '$VERSION'. Use semver (e.g., 3.2.0 or 3.2.0-beta1)." + exit 1 + fi + + - uses: actions/checkout@v4 + with: + ref: ${{ env.BRANCH }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify package.json version matches input + run: | + pkg_ver=$(node -p "require('./package.json').version") + if [ "$pkg_ver" != "$VERSION" ]; then + echo "::error::package.json version ($pkg_ver) does not match requested release version ($VERSION). Merge the prepare-release PR first." + exit 1 + fi + + - name: Verify src/itblBuildInfo.ts version matches + run: | + build_info_ver=$(grep -oE "version:[[:space:]]*'[^']+'" src/itblBuildInfo.ts | head -n1 | sed -E "s/^version:[[:space:]]*'([^']+)'/\1/") + if [ "$build_info_ver" != "$VERSION" ]; then + echo "::error::src/itblBuildInfo.ts version ($build_info_ver) does not match $VERSION." + exit 1 + fi + + - name: Extract changelog section for $VERSION + id: changelog + run: | + section=$(awk -v ver="$VERSION" ' + $0 ~ "^## (\\[)?" ver "(\\])?$" { capturing=1; next } + capturing && /^## (\[)?[0-9]+\.[0-9]+\.[0-9]+/ { exit } + capturing { print } + ' CHANGELOG.md) + + if [ -z "$(printf '%s' "$section" | tr -d '[:space:]')" ]; then + echo "::error::No CHANGELOG.md section found for $VERSION. Expected '## $VERSION' header." + exit 1 + fi + + { + echo 'body<> $GITHUB_OUTPUT + + - name: Fail if tag already exists + run: | + if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then + echo "::error::Git tag '$VERSION' already exists." + exit 1 + fi + if git ls-remote --exit-code --tags origin "$VERSION" >/dev/null 2>&1; then + echo "::error::Git tag '$VERSION' already exists on origin." + exit 1 + fi + + - name: Setup Node.js and npm registry + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .nvmrc + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run tests + run: yarn test --maxWorkers=2 + + - name: Build package + run: yarn prepare + + - name: Create and push git tag + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag "$VERSION" + git push origin "$VERSION" + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BODY: ${{ steps.changelog.outputs.body }} + run: | + prerelease_flag="" + if [ "$SET_PRERELEASE" = "true" ]; then + prerelease_flag="--prerelease" + fi + printf '%s' "$BODY" > release_notes.md + gh release create "$VERSION" \ + --title "$VERSION" \ + --notes-file release_notes.md \ + --target "$BRANCH" \ + $prerelease_flag + + - name: Publish to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --provenance --access public diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b2f4c33..4f67701df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## [Unreleased] + ## 3.1.0 ### Fixes