From de50d001ac4a1466bffbb2433357eb9b1d2fe124 Mon Sep 17 00:00:00 2001 From: Dani K Date: Wed, 12 Nov 2025 12:31:47 +0200 Subject: [PATCH 1/4] podspec must be plain X.Y.Z --- .github/workflows/rc-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rc-release.yml b/.github/workflows/rc-release.yml index ab9e24f..ab4bbee 100644 --- a/.github/workflows/rc-release.yml +++ b/.github/workflows/rc-release.yml @@ -244,8 +244,8 @@ jobs: - name: 📝 Update podspec version run: | VERSION="${{ needs.validate-release.outputs.version }}" - # Remove -rcN suffix but preserve +build for podspec - PODSPEC_VERSION=$(echo $VERSION | sed 's/-rc[0-9]*$//') + # Remove both -rcN and +build suffixes for CocoaPods (podspec must be plain X.Y.Z) + PODSPEC_VERSION=$(echo $VERSION | sed -E 's/(\+[0-9]+)?(-rc[0-9]+)?$//') PODSPEC_FILE="ios/appsflyer_sdk.podspec" From 8eaebe9e779b5b07a42e90afe3b09516488a8209 Mon Sep 17 00:00:00 2001 From: Dani K Date: Wed, 12 Nov 2025 12:37:30 +0200 Subject: [PATCH 2/4] fixing the CI mistake --- ios/appsflyer_sdk.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/appsflyer_sdk.podspec b/ios/appsflyer_sdk.podspec index d844a45..dcca906 100644 --- a/ios/appsflyer_sdk.podspec +++ b/ios/appsflyer_sdk.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'appsflyer_sdk' - s.version = '6.17.7+1' + s.version = '6.17.7' s.summary = 'AppsFlyer Integration for Flutter' s.description = 'AppsFlyer is the market leader in mobile advertising attribution & analytics, helping marketers to pinpoint their targeting, optimize their ad spend and boost their ROI.' s.homepage = 'https://github.com/AppsFlyerSDK/flutter_appsflyer_sdk' From 54b822918b5475891d4f6ca86f9768544034f080 Mon Sep 17 00:00:00 2001 From: Dani K Date: Mon, 17 Nov 2025 15:26:25 +0200 Subject: [PATCH 3/4] CI changes --- .github/workflows/production-release.yml | 31 +- .github/workflows/promote-release.yml | 97 ++++++ .github/workflows/rc-release.yml | 407 +++++++++++++---------- 3 files changed, 348 insertions(+), 187 deletions(-) create mode 100644 .github/workflows/promote-release.yml diff --git a/.github/workflows/production-release.yml b/.github/workflows/production-release.yml index cbfd624..62234f8 100644 --- a/.github/workflows/production-release.yml +++ b/.github/workflows/production-release.yml @@ -26,7 +26,7 @@ name: Production Release - Publish to pub.dev on: - # Trigger when PR to master is merged + # Trigger when PR to master is merged (legacy path; promotion flow now preferred) pull_request: types: - closed @@ -51,6 +51,21 @@ on: type: boolean default: false + # Allow being called from other workflows + workflow_call: + inputs: + version: + required: true + type: string + skip_tests: + required: false + type: boolean + default: false + dry_run: + required: false + type: boolean + default: false + # Ensure only one production release runs at a time concurrency: group: production-release @@ -67,8 +82,8 @@ jobs: name: 🔍 Validate Release runs-on: ubuntu-latest - # Only run if PR was actually merged (not just closed) - if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true + # Run when manually dispatched, called by another workflow, or when a PR merge event happens + if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event.pull_request.merged == true outputs: version: ${{ steps.get-version.outputs.version }} @@ -84,8 +99,8 @@ jobs: - name: 🔍 Validate release source id: validate run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - echo "Manual workflow dispatch - skipping branch validation" + if [[ "${{ github.event_name }}" == "workflow_dispatch" || "${{ github.event_name }}" == "workflow_call" ]]; then + echo "Manual/called run - skipping branch validation" echo "is_release_branch=true" >> $GITHUB_OUTPUT echo "is_valid=true" >> $GITHUB_OUTPUT else @@ -109,9 +124,9 @@ jobs: - name: 📝 Get version from pubspec.yaml id: get-version run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - VERSION="${{ github.event.inputs.version }}" - echo "Using manual version: $VERSION" + if [[ "${{ github.event_name }}" == "workflow_dispatch" || "${{ github.event_name }}" == "workflow_call" ]]; then + VERSION="${{ inputs.version || github.event.inputs.version }}" + echo "Using provided version: $VERSION" else # Extract version from pubspec.yaml VERSION=$(grep "^version:" pubspec.yaml | sed 's/version: //' | tr -d ' ') diff --git a/.github/workflows/promote-release.yml b/.github/workflows/promote-release.yml new file mode 100644 index 0000000..d709649 --- /dev/null +++ b/.github/workflows/promote-release.yml @@ -0,0 +1,97 @@ +name: Promote Release - Merge on QA Pass and Publish + +on: + pull_request: + types: [labeled, synchronize, reopened, ready_for_review] + branches: + - master + pull_request_review: + types: [submitted] + branches: + - master + +concurrency: + group: promote-release-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + gate-and-merge: + name: 🔐 Gate, Verify Checks, and Merge + if: >- + ${ { github.event.pull_request.head.ref } } == '' || startsWith(github.event.pull_request.head.ref, 'releases/') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: read + statuses: read + outputs: + merged: ${{ steps.merge.outputs.merged }} + version: ${{ steps.version.outputs.version }} + steps: + - name: 🧠 Evaluate conditions + id: eval + uses: actions/github-script@v7 + with: + script: | + const core = require('@actions/core'); + const pr = context.payload.pull_request || (await github.rest.pulls.get({owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.pull_request?.number || context.issue.number})).data; + if (!pr) core.setFailed('No PR context'); + const hasLabel = pr.labels.some(l => l.name === 'pass QA ready for deploy'); + if (!hasLabel) core.setFailed('Required label not present: pass QA ready for deploy'); + // Check approvals + const reviews = await github.rest.pulls.listReviews({owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number}); + const approved = reviews.data.some(r => r.state === 'APPROVED'); + if (!approved) core.setFailed('No approval found on the PR'); + core.setOutput('pr_number', pr.number.toString()); + - name: âŗ Wait for required status checks to pass + uses: actions/github-script@v7 + with: + script: | + const prNumber = Number(core.getInput('pr_number', { required: false })) || ${{ steps.eval.outputs.pr_number || '0' }}; + const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber }); + const ref = pr.head.sha; + const start = Date.now(); + const timeoutMs = 60*60*1000; // 60 minutes + const sleep = ms => new Promise(r => setTimeout(r, ms)); + while (true) { + const { data: combined } = await github.rest.repos.getCombinedStatusForRef({ owner: context.repo.owner, repo: context.repo.repo, ref }); + const checksOk = combined.state === 'success'; + if (checksOk) break; + if (Date.now() - start > timeoutMs) throw new Error('Timeout waiting for status checks to pass'); + core.info(`Waiting for checks. Current state: ${combined.state}`); + await sleep(15000); + } + - name: đŸ“Ĩ Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: 🔀 Merge PR immediately + id: merge + uses: actions/github-script@v7 + with: + script: | + const prNumber = Number(${ { steps.eval.outputs.pr_number } }); + const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber }); + if (pr.merged) { core.setOutput('merged', 'true'); return; } + const method = 'merge'; // use repo default merge method + await github.rest.pulls.merge({ owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, merge_method: method }); + core.setOutput('merged', 'true'); + - name: 📝 Read version from pubspec on master + id: version + run: | + git fetch origin master:master + git checkout master + VER=$(grep '^version:' pubspec.yaml | sed 's/version: //' | tr -d ' ') + echo "version=$VER" >> $GITHUB_OUTPUT + + call-production: + name: 🚀 Production Release + needs: gate-and-merge + if: needs.gate-and-merge.outputs.merged == 'true' + uses: ./.github/workflows/production-release.yml + with: + version: ${{ needs.gate-and-merge.outputs.version }} + skip_tests: false + dry_run: false + secrets: inherit diff --git a/.github/workflows/rc-release.yml b/.github/workflows/rc-release.yml index ab4bbee..43ae272 100644 --- a/.github/workflows/rc-release.yml +++ b/.github/workflows/rc-release.yml @@ -29,28 +29,41 @@ name: RC - Release Candidate on: - # Trigger on push to release branches - push: - branches: - - 'releases/**' - - # Allow manual triggering with version specification + # Manual-only triggering with required parameters workflow_dispatch: inputs: - version: - description: 'Release version (e.g., 6.18.0-rc1)' + base_branch: + description: 'Base branch to create the release branch from (e.g., development)' + required: false + default: development + type: string + flutter_version: + description: 'Flutter plugin version for this RC (e.g., 6.18.0-rc1 or 6.18.0+1-rc1)' + required: true + type: string + ios_sdk_version: + description: 'iOS native AppsFlyer SDK version (e.g., 6.17.7)' + required: true + type: string + android_sdk_version: + description: 'Android native AppsFlyer SDK version (e.g., 6.17.4)' required: true type: string + deploy_to_qa: + description: 'Open PR to master and publish RC to pub.dev' + required: false + type: boolean + default: false skip_tests: - description: 'Skip tests and builds (for testing workflow only)' + description: 'Skip reusable CI when running this workflow (PR and production flows still run CI)' required: false type: boolean default: false # Prevent multiple RC workflows from running simultaneously concurrency: - group: rc-release-${{ github.ref }} - cancel-in-progress: false # Don't cancel, let it finish + group: rc-release-${{ github.run_id }}-${{ github.event.inputs.flutter_version || github.ref }} + cancel-in-progress: true jobs: # =========================================================================== @@ -60,93 +73,69 @@ jobs: # =========================================================================== validate-release: - name: 🔍 Validate Release Branch + name: 🔍 Validate Inputs & Compute Branch runs-on: ubuntu-latest outputs: - version: ${{ steps.extract-version.outputs.version }} - is_rc: ${{ steps.extract-version.outputs.is_rc }} - is_valid: ${{ steps.extract-version.outputs.is_valid }} + version: ${{ steps.compute.outputs.version }} + base_version: ${{ steps.compute.outputs.base_version }} + podspec_version: ${{ steps.compute.outputs.podspec_version }} + is_rc: ${{ steps.compute.outputs.is_rc }} + is_valid: ${{ steps.compute.outputs.is_valid }} + base_branch: ${{ steps.compute.outputs.base_branch }} + release_branch: ${{ steps.compute.outputs.release_branch }} + ios_sdk_version: ${{ steps.compute.outputs.ios_sdk_version }} + android_sdk_version: ${{ steps.compute.outputs.android_sdk_version }} + deploy_to_qa: ${{ steps.compute.outputs.deploy_to_qa }} steps: - name: đŸ“Ĩ Checkout repository uses: actions/checkout@v4 - - name: 🔍 Extract and validate version - id: extract-version + - name: 🔍 Validate and compute + id: compute run: | - # Determine version from branch name or manual input - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - VERSION="${{ github.event.inputs.version }}" - echo "Using manual version: $VERSION" - else - # Extract version from branch name: releases/X.x.x/X.Y.x/X.Y.Z[-rcN][+build] - # Examples: - # - releases/6.x.x/6.18.x/6.18.0-rc1 - # - releases/6.x.x/6.17.x/6.17.4+1-rc1 - # - releases/6.x.x/6.17.x/6.17.4+1 - BRANCH_NAME="${{ github.ref_name }}" - echo "Branch name: $BRANCH_NAME" - - # Extract version using regex - now supports +build suffix - # Pattern: releases/X.x.x/X.Y.x/X.Y.Z[+build][-rcN] where x is literal 'x' - if [[ $BRANCH_NAME =~ releases/([0-9]+)\.x\.x/([0-9]+\.[0-9]+)\.x/([0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?(-rc[0-9]+)?)$ ]]; then - VERSION="${BASH_REMATCH[3]}" - MAJOR="${BASH_REMATCH[1]}" - MAJOR_MINOR="${BASH_REMATCH[2]}" - - echo "Extracted version: $VERSION" - echo "Major version: $MAJOR" - echo "Major.Minor: $MAJOR_MINOR" - - # Validate that version starts with the correct major.minor (before any +build suffix) - VERSION_PREFIX=$(echo "$VERSION" | grep -oE '^[0-9]+\.[0-9]+') - if [[ "$VERSION_PREFIX" != "$MAJOR_MINOR" ]]; then - echo "❌ Version mismatch!" - echo "Expected version to start with: $MAJOR_MINOR" - echo "Got: $VERSION" - exit 1 - fi - - # Validate that major version matches - VERSION_MAJOR=$(echo "$VERSION" | grep -oE '^[0-9]+') - if [[ "$VERSION_MAJOR" != "$MAJOR" ]]; then - echo "❌ Major version mismatch!" - echo "Expected major version: $MAJOR" - echo "Got: $VERSION_MAJOR" - exit 1 - fi - else - echo "❌ Invalid branch name format!" - echo "Expected: releases/X.x.x/X.Y.x/X.Y.Z[-rcN][+build]" - echo "Examples:" - echo " - releases/6.x.x/6.18.x/6.18.0-rc1" - echo " - releases/6.x.x/6.17.x/6.17.4+1-rc1" - echo " - releases/6.x.x/6.17.x/6.17.4+1" - echo "Got: $BRANCH_NAME" - exit 1 - fi + set -euo pipefail + VERSION="${{ github.event.inputs.flutter_version }}" + IOS_VER="${{ github.event.inputs.ios_sdk_version }}" + AND_VER="${{ github.event.inputs.android_sdk_version }}" + BASE_BRANCH_INPUT="${{ github.event.inputs.base_branch }}" + DEPLOY_TO_QA="${{ github.event.inputs.deploy_to_qa }}" + + if [[ -z "$VERSION" || -z "$IOS_VER" || -z "$AND_VER" ]]; then + echo "❌ Missing required inputs"; exit 1 fi - - # Validate version format (supports +build suffix) - if [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?(-rc[0-9]+)?$ ]]; then - echo "✅ Valid version format: $VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "is_valid=true" >> $GITHUB_OUTPUT - - # Check if it's an RC version - if [[ $VERSION =~ -rc[0-9]+$ ]]; then - echo "is_rc=true" >> $GITHUB_OUTPUT - echo "đŸ“Ļ This is a Release Candidate" - else - echo "is_rc=false" >> $GITHUB_OUTPUT - echo "đŸ“Ļ This is a production version" - fi - else - echo "❌ Invalid version format: $VERSION" - echo "is_valid=false" >> $GITHUB_OUTPUT - exit 1 + + # Validate formats + if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?-rc[0-9]+$ ]]; then + echo "❌ flutter_version must be a prerelease like X.Y.Z[-build]+?-rcN (e.g., 6.18.0-rc1 or 6.18.0+1-rc1)"; exit 1 fi + if [[ ! $IOS_VER =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ ios_sdk_version must be X.Y.Z"; exit 1 + fi + if [[ ! $AND_VER =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ android_sdk_version must be X.Y.Z"; exit 1 + fi + + # Compute base version (remove -rcN), keep +build if present + BASE_VERSION=$(echo "$VERSION" | sed 's/-rc[0-9]*$//') + # Podspec version must remove both +build and -rcN + PODSPEC_VERSION=$(echo "$VERSION" | sed -E 's/(\+[0-9]+)?(-rc[0-9]+)?$//') + + MAJOR_MINOR=$(echo "$BASE_VERSION" | grep -oE '^[0-9]+\.[0-9]+') + MAJOR=$(echo "$BASE_VERSION" | grep -oE '^[0-9]+') + RELEASE_BRANCH="releases/${MAJOR}.x.x/${MAJOR_MINOR}.x/${VERSION}" + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "base_version=$BASE_VERSION" >> $GITHUB_OUTPUT + echo "podspec_version=$PODSPEC_VERSION" >> $GITHUB_OUTPUT + echo "is_rc=true" >> $GITHUB_OUTPUT + echo "is_valid=true" >> $GITHUB_OUTPUT + echo "base_branch=$BASE_BRANCH_INPUT" >> $GITHUB_OUTPUT + echo "release_branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT + echo "ios_sdk_version=$IOS_VER" >> $GITHUB_OUTPUT + echo "android_sdk_version=$AND_VER" >> $GITHUB_OUTPUT + echo "deploy_to_qa=$DEPLOY_TO_QA" >> $GITHUB_OUTPUT # =========================================================================== # Job 2: Run CI Pipeline @@ -162,121 +151,120 @@ jobs: secrets: inherit # =========================================================================== - # Job 3: Update Version Files - # =========================================================================== - # Updates pubspec.yaml and other version-related files + # Job 3: Create/Update Release Branch and Apply Changes # =========================================================================== - update-version: - name: 📝 Update Version Files + prepare-branch: + name: đŸŒŋ Create Release Branch & Apply Changes runs-on: ubuntu-latest needs: [validate-release, run-ci] if: always() && needs.validate-release.outputs.is_valid == 'true' - + outputs: + release_branch: ${{ steps.push.outputs.release_branch }} steps: - - name: đŸ“Ĩ Checkout repository + - name: đŸ“Ĩ Checkout base branch uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 # Fetch all history for proper tagging - + ref: ${{ needs.validate-release.outputs.base_branch }} + fetch-depth: 0 + - name: 🔧 Setup Flutter SDK uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true - - - name: 📝 Update pubspec.yaml version + + - name: đŸŒŋ Create release branch + id: branch run: | - VERSION="${{ needs.validate-release.outputs.version }}" - - # Remove -rcN suffix for pubspec.yaml (pub.dev doesn't support pre-release tags) - # But preserve +build suffix if present (pub.dev supports build numbers) - PUBSPEC_VERSION=$(echo $VERSION | sed 's/-rc[0-9]*$//') - - echo "Full version: $VERSION" - echo "Updating pubspec.yaml to version: $PUBSPEC_VERSION" - - # Update version in pubspec.yaml - sed -i.bak "s/^version: .*/version: $PUBSPEC_VERSION/" pubspec.yaml + set -e + REL_BRANCH="${{ needs.validate-release.outputs.release_branch }}" + echo "Target release branch: $REL_BRANCH" + if git ls-remote --exit-code --heads origin "$REL_BRANCH" >/dev/null 2>&1; then + echo "Branch already exists on remote. Checking it out." + git fetch origin "$REL_BRANCH":"$REL_BRANCH" + git checkout "$REL_BRANCH" + else + git checkout -b "$REL_BRANCH" + fi + + - name: 📝 Update pubspec.yaml version (RC full) + run: | + VERSION='${{ needs.validate-release.outputs.version }}' + echo "Setting pubspec.yaml version to $VERSION (includes -rcN)" + sed -i.bak "s/^version: .*/version: $VERSION/" pubspec.yaml rm pubspec.yaml.bak - - # Verify the change - echo "Updated pubspec.yaml:" grep "^version:" pubspec.yaml - - - name: 📝 Update Android plugin version constant + + - name: 📝 Update Android SDK dependency run: | - VERSION="${{ needs.validate-release.outputs.version }}" - - # Find and update kPluginVersion in Android constants - ANDROID_CONSTANTS_FILE="android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerConstants.java" - - if [ -f "$ANDROID_CONSTANTS_FILE" ]; then - echo "Updating Android plugin version to: $VERSION" - sed -i.bak "s/kPluginVersion = \".*\"/kPluginVersion = \"$VERSION\"/" "$ANDROID_CONSTANTS_FILE" - rm "${ANDROID_CONSTANTS_FILE}.bak" - - echo "Updated Android constants:" - grep "kPluginVersion" "$ANDROID_CONSTANTS_FILE" || echo "Pattern not found" - else - echo "âš ī¸ Android constants file not found, skipping" - fi - - - name: 📝 Update iOS plugin version constant + AND_VER='${{ needs.validate-release.outputs.android_sdk_version }}' + sed -i.bak "s/com.appsflyer:af-android-sdk:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*/com.appsflyer:af-android-sdk:${AND_VER}/" android/build.gradle + rm android/build.gradle.bak + grep "af-android-sdk:" -n android/build.gradle | head -1 + + - name: 📝 Update iOS podspec version and dependencies run: | - VERSION="${{ needs.validate-release.outputs.version }}" - - # Find and update kPluginVersion in iOS constants - IOS_PLUGIN_FILE="ios/Classes/AppsflyerSdkPlugin.m" - - if [ -f "$IOS_PLUGIN_FILE" ]; then - echo "Updating iOS plugin version to: $VERSION" - sed -i.bak "s/kPluginVersion = @\".*\"/kPluginVersion = @\"$VERSION\"/" "$IOS_PLUGIN_FILE" - rm "${IOS_PLUGIN_FILE}.bak" - - echo "Updated iOS plugin file:" - grep "kPluginVersion" "$IOS_PLUGIN_FILE" || echo "Pattern not found" + PODSPEC_VERSION='${{ needs.validate-release.outputs.podspec_version }}' + IOS_VER='${{ needs.validate-release.outputs.ios_sdk_version }}' + FILE='ios/appsflyer_sdk.podspec' + if [ -f "$FILE" ]; then + sed -i.bak "s/s\.version\s*=\s*'.*'/s.version = '${PODSPEC_VERSION}'/" "$FILE" + sed -i.bak "s/ss\.ios\.dependency 'AppsFlyerFramework','[^']*'/ss.ios.dependency 'AppsFlyerFramework','${IOS_VER}'/" "$FILE" + # PurchaseConnector line may or may not exist + if grep -q "PurchaseConnector', '" "$FILE"; then + sed -i.bak "s/ss\.ios\.dependency 'PurchaseConnector', '[^']*'/ss.ios.dependency 'PurchaseConnector', '${IOS_VER}'/" "$FILE" || true + fi + rm ${FILE}.bak || true + echo "Updated podspec lines:" + grep -n "s.version\|AppsFlyerFramework\|PurchaseConnector" "$FILE" || true else - echo "âš ī¸ iOS plugin file not found, skipping" + echo "âš ī¸ $FILE not found" fi - - - name: 📝 Update podspec version + + - name: 📝 Update plugin version constants (Android/iOS) run: | - VERSION="${{ needs.validate-release.outputs.version }}" - # Remove both -rcN and +build suffixes for CocoaPods (podspec must be plain X.Y.Z) - PODSPEC_VERSION=$(echo $VERSION | sed -E 's/(\+[0-9]+)?(-rc[0-9]+)?$//') - - PODSPEC_FILE="ios/appsflyer_sdk.podspec" - - if [ -f "$PODSPEC_FILE" ]; then - echo "Full version: $VERSION" - echo "Updating podspec to version: $PODSPEC_VERSION" - sed -i.bak "s/s\.version.*=.*/s.version = '$PODSPEC_VERSION'/" "$PODSPEC_FILE" - rm "${PODSPEC_FILE}.bak" - - echo "Updated podspec:" - grep "s.version" "$PODSPEC_FILE" - else - echo "âš ī¸ Podspec file not found, skipping" + VERSION='${{ needs.validate-release.outputs.version }}' + AND_FILE="android/src/main/java/com/appsflyer/appsflyersdk/AppsflyerConstants.java" + IOS_FILE="ios/Classes/AppsflyerSdkPlugin.m" + if [ -f "$AND_FILE" ]; then + sed -i.bak "s/kPluginVersion = \".*\"/kPluginVersion = \"$VERSION\"/" "$AND_FILE" && rm "$AND_FILE.bak" || true fi - - - name: 💾 Commit version changes + if [ -f "$IOS_FILE" ]; then + sed -i.bak "s/kPluginVersion = @\".*\"/kPluginVersion = @\"$VERSION\"/" "$IOS_FILE" && rm "$IOS_FILE.bak" || true + fi + + - name: 📝 Update README SDK versions run: | - VERSION="${{ needs.validate-release.outputs.version }}" - - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - - # Check if there are changes to commit + IOS_VER='${{ needs.validate-release.outputs.ios_sdk_version }}' + AND_VER='${{ needs.validate-release.outputs.android_sdk_version }}' + sed -i.bak -E "s/- Android AppsFlyer SDK \*\*v[0-9.]+\*\*/- Android AppsFlyer SDK **v${AND_VER}**/" README.md + sed -i.bak -E "s/- iOS AppsFlyer SDK \*\*v[0-9.]+\*\*/- iOS AppsFlyer SDK **v${IOS_VER}**/" README.md + rm README.md.bak + echo "README updated SDK versions:" + sed -n '12,20p' README.md | cat + + - name: 💾 Commit & push changes + id: push + run: | + set -e + REL_BRANCH='${{ needs.validate-release.outputs.release_branch }}' + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" if [[ -n $(git status -s) ]]; then - git add pubspec.yaml android/ ios/ - git commit -m "chore: bump version to $VERSION" - git push - echo "✅ Version changes committed and pushed" + git add pubspec.yaml android/ ios/ README.md || true + git commit -m "chore: prepare RC ${VERSION} (iOS ${{ needs.validate-release.outputs.ios_sdk_version }}, Android ${{ needs.validate-release.outputs.android_sdk_version }})" + git push --set-upstream origin "$REL_BRANCH" else - echo "â„šī¸ No version changes to commit" + echo "No changes to commit" + # Ensure branch exists on remote + if ! git ls-remote --exit-code --heads origin "$REL_BRANCH" >/dev/null 2>&1; then + git push --set-upstream origin "$REL_BRANCH" + fi fi + echo "release_branch=$REL_BRANCH" >> $GITHUB_OUTPUT + + # (Deprecated) Legacy update-version job removed; handled by prepare-branch # =========================================================================== # Job 4: Create Pre-Release @@ -287,15 +275,15 @@ jobs: create-prerelease: name: đŸˇī¸ Create Pre-Release runs-on: ubuntu-latest - needs: [validate-release, run-ci, update-version] - if: always() && needs.validate-release.outputs.is_rc == 'true' + needs: [validate-release, run-ci, prepare-branch] + if: always() && needs.validate-release.outputs.is_rc == 'true' && needs.validate-release.outputs.deploy_to_qa == 'true' steps: - name: đŸ“Ĩ Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.ref }} + ref: ${{ needs.prepare-branch.outputs.release_branch }} - name: 📝 Generate release notes id: release-notes @@ -361,6 +349,67 @@ jobs: # Sends notification to Slack channel about the RC release # =========================================================================== + open-pr: + name: 🔀 Open PR to master + runs-on: ubuntu-latest + needs: [validate-release, prepare-branch] + if: always() && needs.validate-release.outputs.deploy_to_qa == 'true' + steps: + - name: đŸ“Ĩ Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.prepare-branch.outputs.release_branch }} + - name: 🧠 Create or update PR + uses: actions/github-script@v7 + with: + script: | + const version = `${{ toJSON(needs.validate-release.outputs.base_version) }}`; + const head = `${{ toJSON(needs.prepare-branch.outputs.release_branch) }}`; + const base = 'master'; + // Find existing PR + const { data: prs } = await github.rest.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', head: `${context.repo.owner}:${head}` }); + const body = `### Release ${version}\n\n- Android SDK: ${{ needs.validate-release.outputs.android_sdk_version }}\n- iOS SDK: ${{ needs.validate-release.outputs.ios_sdk_version }}\n\nThis PR was opened by the RC workflow.`; + if (prs.length > 0) { + const pr = prs[0]; + await github.rest.pulls.update({ owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, title: `Release ${version}`, body }); + core.setOutput('pr_number', pr.number); + } else { + const { data: pr } = await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, head, base, title: `Release ${version}`, body, maintainer_can_modify: true }); + core.setOutput('pr_number', pr.number); + } + + publish-rc: + name: đŸ“Ļ Publish RC to pub.dev + runs-on: ubuntu-latest + needs: [validate-release, prepare-branch] + if: always() && needs.validate-release.outputs.deploy_to_qa == 'true' + steps: + - name: đŸ“Ĩ Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.prepare-branch.outputs.release_branch }} + - name: 🔧 Setup Flutter SDK + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + - name: đŸ“Ļ Get dependencies + run: flutter pub get + - name: 📝 Validate package (dry-run) + run: flutter pub publish --dry-run + - name: 🚀 Publish RC to pub.dev + env: + PUB_DEV_CREDENTIALS: ${{ secrets.PUB_DEV_CREDENTIALS }} + run: | + if [[ -z "${PUB_DEV_CREDENTIALS}" ]]; then + echo "PUB_DEV_CREDENTIALS is missing"; exit 1; fi + mkdir -p ~/.config/dart + echo "${PUB_DEV_CREDENTIALS}" > ~/.config/dart/pub-credentials.json + flutter pub publish --force + rm -f ~/.config/dart/pub-credentials.json + notify-team: name: đŸ“ĸ Notify Team runs-on: ubuntu-latest From de6a93044b30a2298a8949b04e4f0c351807ea05 Mon Sep 17 00:00:00 2001 From: Dani K Date: Wed, 19 Nov 2025 10:23:40 +0200 Subject: [PATCH 4/4] adding a dry run option --- .github/workflows/rc-release.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rc-release.yml b/.github/workflows/rc-release.yml index 43ae272..6c3efdf 100644 --- a/.github/workflows/rc-release.yml +++ b/.github/workflows/rc-release.yml @@ -59,6 +59,11 @@ on: required: false type: boolean default: false + dry_run: + description: 'Do not publish RC to pub.dev (still opens PR and creates prerelease)' + required: false + type: boolean + default: true # Prevent multiple RC workflows from running simultaneously concurrency: @@ -87,6 +92,7 @@ jobs: ios_sdk_version: ${{ steps.compute.outputs.ios_sdk_version }} android_sdk_version: ${{ steps.compute.outputs.android_sdk_version }} deploy_to_qa: ${{ steps.compute.outputs.deploy_to_qa }} + dry_run: ${{ steps.compute.outputs.dry_run }} steps: - name: đŸ“Ĩ Checkout repository @@ -101,6 +107,7 @@ jobs: AND_VER="${{ github.event.inputs.android_sdk_version }}" BASE_BRANCH_INPUT="${{ github.event.inputs.base_branch }}" DEPLOY_TO_QA="${{ github.event.inputs.deploy_to_qa }}" + DRY_RUN_INPUT="${{ github.event.inputs.dry_run }}" if [[ -z "$VERSION" || -z "$IOS_VER" || -z "$AND_VER" ]]; then echo "❌ Missing required inputs"; exit 1 @@ -136,6 +143,7 @@ jobs: echo "ios_sdk_version=$IOS_VER" >> $GITHUB_OUTPUT echo "android_sdk_version=$AND_VER" >> $GITHUB_OUTPUT echo "deploy_to_qa=$DEPLOY_TO_QA" >> $GITHUB_OUTPUT + echo "dry_run=$DRY_RUN_INPUT" >> $GITHUB_OUTPUT # =========================================================================== # Job 2: Run CI Pipeline @@ -399,7 +407,12 @@ jobs: run: flutter pub get - name: 📝 Validate package (dry-run) run: flutter pub publish --dry-run + - name: â„šī¸ RC dry-run active — skipping publish + if: ${{ needs.validate-release.outputs.dry_run == 'true' }} + run: | + echo "RC dry_run is true — will not publish to pub.dev." - name: 🚀 Publish RC to pub.dev + if: ${{ needs.validate-release.outputs.dry_run != 'true' }} env: PUB_DEV_CREDENTIALS: ${{ secrets.PUB_DEV_CREDENTIALS }} run: | @@ -570,7 +583,7 @@ jobs: rc-summary: name: 📋 RC Summary runs-on: ubuntu-latest - needs: [validate-release, run-ci, update-version, create-prerelease] + needs: [validate-release, run-ci, prepare-branch, create-prerelease] if: always() steps: @@ -585,7 +598,7 @@ jobs: echo "-----------------------------------------" echo "Validation: ${{ needs.validate-release.result }}" echo "CI Pipeline: ${{ needs.run-ci.result }}" - echo "Version Update: ${{ needs.update-version.result }}" + echo "Prepare Branch: ${{ needs.prepare-branch.result }}" echo "Pre-Release: ${{ needs.create-prerelease.result }}" echo "========================================="