mirror of
https://github.com/selfxyz/self.git
synced 2026-01-10 23:27:56 -05:00
SELF-832: tweak mobile deploy auto bump version pr feature; v2.7.0 (#1244)
* bump version to match staging * save wip * deploy fixes * fix version setting * update version logic * fix version pr * increase timeout to 2 hours * pr logic tweaks * fix script * fix script path * add comments and update logic to test from feature branch * fix build path * fix version input error * fix pulling version * add skip-deploy lable * address cr concners
This commit is contained in:
@@ -25,28 +25,24 @@ runs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Check if there are changes
|
||||
git add ${{ inputs.file_paths }}
|
||||
if [[ -z $(git status -s) ]]; then
|
||||
echo "No changes to commit. Skipping PR creation."
|
||||
# Ensure we're on staging branch, not detached HEAD
|
||||
git fetch origin staging dev
|
||||
git checkout staging
|
||||
|
||||
# Check if staging has commits not in dev (version bumps + any build changes)
|
||||
COMMITS_AHEAD=$(git rev-list --count origin/dev..staging)
|
||||
|
||||
if [ "$COMMITS_AHEAD" -eq 0 ]; then
|
||||
echo "ℹ️ No new commits on staging compared to dev. Skipping PR creation."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Commit changes on temporary branch
|
||||
git checkout -b temp-version-commit
|
||||
git commit -m "chore: bump ${{ inputs.platform }} version for ${{ inputs.version }} [skip ci]"
|
||||
echo "📊 Staging is $COMMITS_AHEAD commit(s) ahead of dev"
|
||||
|
||||
# Create new branch from dev
|
||||
git fetch origin dev
|
||||
git checkout -b ${BRANCH_NAME} origin/dev
|
||||
# Create new branch from current staging (which has all version changes)
|
||||
git checkout -b ${BRANCH_NAME}
|
||||
|
||||
# Cherry-pick only the version changes
|
||||
git cherry-pick temp-version-commit
|
||||
|
||||
# Clean up temporary branch
|
||||
git branch -D temp-version-commit
|
||||
|
||||
# Push and create PR
|
||||
# Push the branch
|
||||
git push --set-upstream origin ${BRANCH_NAME}
|
||||
|
||||
# Determine PR title based on platform
|
||||
@@ -60,6 +56,7 @@ runs:
|
||||
--base dev \
|
||||
--head ${BRANCH_NAME} \
|
||||
--title "$PR_TITLE" \
|
||||
--body "Automated version bump by CI"
|
||||
--body "Automated version bump by CI" \
|
||||
--label "automated"
|
||||
env:
|
||||
GH_TOKEN: ${{ inputs.github_token }}
|
||||
|
||||
7
.github/actions/get-version/action.yml
vendored
7
.github/actions/get-version/action.yml
vendored
@@ -7,11 +7,18 @@ inputs:
|
||||
description: "Path to the app directory"
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: "Extracted app version from package.json"
|
||||
value: ${{ steps.get-version.outputs.version }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Get version from package.json
|
||||
id: get-version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(node -p "require('${{ inputs.app_path }}/package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
578
.github/workflows/mobile-deploy.yml
vendored
578
.github/workflows/mobile-deploy.yml
vendored
@@ -3,6 +3,30 @@ name: Mobile Deploy
|
||||
# 1. Manual trigger (workflow_dispatch) with configurable options
|
||||
# 2. When PRs are merged to staging (auto-deploy to internal track)
|
||||
# 3. DISABLED: When called by other workflows (workflow_call)
|
||||
#
|
||||
# === PR LABELS ===
|
||||
# - skip-deploy: Skip deployment entirely (no version bump, no builds)
|
||||
# - version:major/minor/patch: Control version bump type
|
||||
# - ios-only/android-only: Build only one platform
|
||||
#
|
||||
# === WORKFLOW LOGIC ===
|
||||
# Build Branch: Always builds from the branch that triggered the workflow
|
||||
# - PR merges: Builds from 'staging' branch (the branch being merged into)
|
||||
# - Manual dispatch: Builds from the branch where workflow was manually triggered
|
||||
# - This allows testing from feature branches before merging to dev/staging
|
||||
#
|
||||
# Version Bump PR: After successful build, creates PR to bump version
|
||||
# - Default target: 'dev' branch (can be overridden with bump_target_branch input)
|
||||
# - Workflow checks out the target branch, applies version changes, and creates PR
|
||||
# - This separates the build source from the version bump destination
|
||||
#
|
||||
# Example flows:
|
||||
# 1. Normal production flow:
|
||||
# - Merge PR to staging → builds from staging → creates version bump PR to dev
|
||||
# 2. Testing from feature branch:
|
||||
# - Manually trigger from feature branch → builds from feature branch → creates version bump PR to dev
|
||||
# 3. Custom version bump target:
|
||||
# - Set bump_target_branch input → creates version bump PR to specified branch instead of dev
|
||||
|
||||
env:
|
||||
# Build environment versions
|
||||
@@ -71,6 +95,16 @@ on:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
dry_run:
|
||||
description: "Do not commit/push or create PR/tags"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
bump_target_branch:
|
||||
description: "Target branch for version bump PR (default: dev). NOTE: This is where the version bump PR will be created, NOT the branch to build from. The workflow always builds from the triggering branch."
|
||||
required: false
|
||||
type: string
|
||||
default: "dev"
|
||||
|
||||
pull_request:
|
||||
types: [closed]
|
||||
@@ -105,11 +139,110 @@ on:
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
# Group by deployment track or ref name to allow different tracks to run in parallel
|
||||
# cancel-in-progress: false ensures we don't cancel ongoing deployments
|
||||
# Branch-locking in create-version-bump-pr prevents duplicate PRs for same version
|
||||
group: mobile-deploy-${{ inputs.deployment_track || github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Bump version atomically before platform builds to avoid race conditions
|
||||
# NOTE: Checks out the triggering branch (staging for PR merges, or the branch where manually triggered)
|
||||
bump-version:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'skip-deploy')
|
||||
outputs:
|
||||
version: ${{ steps.bump.outputs.version }}
|
||||
ios_build: ${{ steps.bump.outputs.ios_build }}
|
||||
android_build: ${{ steps.bump.outputs.android_build }}
|
||||
version_bump_type: ${{ steps.determine-bump.outputs.version_bump }}
|
||||
platform: ${{ steps.determine-platform.outputs.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Build from the branch that triggered the workflow (staging, feature branch, etc.)
|
||||
ref: ${{ github.ref_name }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Determine version bump from PR labels or input
|
||||
id: determine-bump
|
||||
run: |
|
||||
VERSION_BUMP="${{ inputs.version_bump || 'build' }}"
|
||||
|
||||
# Override with PR label if present
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
|
||||
if echo "$LABELS" | grep -q "version:major"; then
|
||||
VERSION_BUMP="major"
|
||||
elif echo "$LABELS" | grep -q "version:minor"; then
|
||||
VERSION_BUMP="minor"
|
||||
elif echo "$LABELS" | grep -q "version:patch"; then
|
||||
VERSION_BUMP="patch"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "version_bump=$VERSION_BUMP" >> $GITHUB_OUTPUT
|
||||
echo "📦 Version bump type: $VERSION_BUMP"
|
||||
|
||||
- name: Determine platform from labels or input
|
||||
id: determine-platform
|
||||
run: |
|
||||
PLATFORM="both"
|
||||
|
||||
# Check workflow input first
|
||||
if [ -n "${{ inputs.platform }}" ]; then
|
||||
INPUT_PLATFORM="${{ inputs.platform }}"
|
||||
if [ "$INPUT_PLATFORM" = "ios" ]; then
|
||||
PLATFORM="ios"
|
||||
elif [ "$INPUT_PLATFORM" = "android" ]; then
|
||||
PLATFORM="android"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Override with PR labels if present
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
|
||||
if echo "$LABELS" | grep -q "ios-only"; then
|
||||
PLATFORM="ios"
|
||||
elif echo "$LABELS" | grep -q "android-only"; then
|
||||
PLATFORM="android"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "platform=$PLATFORM" >> $GITHUB_OUTPUT
|
||||
echo "📱 Platform to deploy: $PLATFORM"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Bump version using version-manager script
|
||||
id: bump
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
VERSION_BUMP="${{ steps.determine-bump.outputs.version_bump }}"
|
||||
PLATFORM="${{ steps.determine-platform.outputs.platform }}"
|
||||
|
||||
echo "🔄 Calculating version bump..."
|
||||
echo " Type: $VERSION_BUMP"
|
||||
echo " Platform: $PLATFORM"
|
||||
echo ""
|
||||
|
||||
# Use version-manager script to calculate bump
|
||||
# NOTE: Using absolute path to ensure script is found regardless of CWD
|
||||
node ${{ env.APP_PATH }}/scripts/version-manager.cjs bump "$VERSION_BUMP" "$PLATFORM"
|
||||
|
||||
echo ""
|
||||
echo "✅ Version bump calculated successfully"
|
||||
echo "⚠️ Note: Changes are local only. Will be committed in PR after successful builds."
|
||||
|
||||
build-ios:
|
||||
needs: [bump-version]
|
||||
runs-on: macos-latest-large
|
||||
if: |
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
@@ -135,9 +268,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# When triggered by PR merge, use the merge commit on staging
|
||||
# This ensures we deploy exactly what landed on staging (including version.json from source + any conflict resolutions)
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || 'staging' }}
|
||||
# Checkout the branch that triggered the workflow
|
||||
ref: ${{ github.ref_name }}
|
||||
- name: Read and sanitize Node.js version
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -152,26 +284,6 @@ jobs:
|
||||
echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Determine version bump from PR labels or input
|
||||
id: version-bump
|
||||
run: |
|
||||
VERSION_BUMP="${{ inputs.version_bump || 'build' }}"
|
||||
|
||||
# Override with PR label if present
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
|
||||
if echo "$LABELS" | grep -q "version:major"; then
|
||||
VERSION_BUMP="major"
|
||||
elif echo "$LABELS" | grep -q "version:minor"; then
|
||||
VERSION_BUMP="minor"
|
||||
elif echo "$LABELS" | grep -q "version:patch"; then
|
||||
VERSION_BUMP="patch"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "version_bump=$VERSION_BUMP" >> $GITHUB_OUTPUT
|
||||
echo "📦 Version bump type: $VERSION_BUMP"
|
||||
|
||||
- name: Verify branch and commit (iOS)
|
||||
if: inputs.platform != 'android'
|
||||
run: |
|
||||
@@ -179,21 +291,37 @@ jobs:
|
||||
echo "Current branch: $(git branch --show-current || git symbolic-ref --short HEAD 2>/dev/null || echo 'detached')"
|
||||
echo "Current commit: $(git rev-parse HEAD)"
|
||||
echo "Current commit message: $(git log -1 --pretty=format:'%s')"
|
||||
echo "Staging HEAD commit: $(git rev-parse origin/staging)"
|
||||
echo "Staging HEAD message: $(git log -1 --pretty=format:'%s' origin/staging)"
|
||||
BUILD_BRANCH="${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }}"
|
||||
echo "Building from branch: $BUILD_BRANCH"
|
||||
echo "Target HEAD commit: $(git rev-parse origin/$BUILD_BRANCH)"
|
||||
echo "Target HEAD message: $(git log -1 --pretty=format:'%s' origin/$BUILD_BRANCH)"
|
||||
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
echo "📌 Building from merge commit on staging (includes source + conflict resolutions)"
|
||||
echo "PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}"
|
||||
echo "Merge commit includes version.json from source branch with bumped build numbers"
|
||||
elif [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/staging)" ]; then
|
||||
echo "⚠️ WARNING: Current commit differs from latest staging commit"
|
||||
echo "This might indicate we're not building from the latest staging branch"
|
||||
git log --oneline HEAD..origin/staging || true
|
||||
elif [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/$BUILD_BRANCH)" ]; then
|
||||
echo "⚠️ WARNING: Current commit differs from latest $BUILD_BRANCH commit"
|
||||
echo "This might indicate we're not building from the latest $BUILD_BRANCH branch"
|
||||
git log --oneline HEAD..origin/$BUILD_BRANCH || true
|
||||
else
|
||||
echo "✅ Building from latest staging commit"
|
||||
echo "✅ Building from latest $BUILD_BRANCH commit"
|
||||
fi
|
||||
|
||||
- name: Apply version bump for build
|
||||
if: inputs.platform != 'android'
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
VERSION="${{ needs.bump-version.outputs.version }}"
|
||||
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
|
||||
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
|
||||
|
||||
echo "📝 Applying version bump for iOS build: $VERSION (iOS Build: $IOS_BUILD, Android Build: $ANDROID_BUILD)"
|
||||
|
||||
# Use version-manager script to apply versions
|
||||
node ${{ env.APP_PATH }}/scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"
|
||||
|
||||
- name: Set up Xcode
|
||||
if: inputs.platform != 'android'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
@@ -510,6 +638,9 @@ jobs:
|
||||
- name: Build and upload to App Store Connect/TestFlight
|
||||
if: inputs.platform != 'android' && !env.ACT
|
||||
env:
|
||||
CI_VERSION: ${{ needs.bump-version.outputs.version }}
|
||||
CI_IOS_BUILD: ${{ needs.bump-version.outputs.ios_build }}
|
||||
CI_ANDROID_BUILD: ${{ needs.bump-version.outputs.android_build }}
|
||||
ENABLE_DEBUG_LOGS: ${{ secrets.ENABLE_DEBUG_LOGS }}
|
||||
GRAFANA_LOKI_PASSWORD: ${{ secrets.GRAFANA_LOKI_PASSWORD }}
|
||||
GRAFANA_LOKI_URL: ${{ secrets.GRAFANA_LOKI_URL }}
|
||||
@@ -546,29 +677,62 @@ jobs:
|
||||
|
||||
# Determine deployment track and version bump
|
||||
DEPLOYMENT_TRACK="${{ inputs.deployment_track || 'internal' }}"
|
||||
VERSION_BUMP="${{ steps.version-bump.outputs.version_bump }}"
|
||||
VERSION_BUMP="${{ needs.bump-version.outputs.version_bump_type }}"
|
||||
TEST_MODE="${{ inputs.test_mode || false }}"
|
||||
|
||||
echo "📱 Deployment Configuration:"
|
||||
echo " - Track: $DEPLOYMENT_TRACK"
|
||||
echo " - Version Bump: $VERSION_BUMP"
|
||||
echo " - Version Bump: $VERSION_BUMP (already applied in bump-version job)"
|
||||
echo " - Version: ${{ needs.bump-version.outputs.version }}"
|
||||
echo " - iOS Build: ${{ needs.bump-version.outputs.ios_build }}"
|
||||
echo " - Test Mode: $TEST_MODE"
|
||||
|
||||
if [ "$TEST_MODE" = "true" ]; then
|
||||
echo "🧪 Running in TEST MODE - will skip upload to TestFlight"
|
||||
bundle exec fastlane ios deploy_auto \
|
||||
deployment_track:$DEPLOYMENT_TRACK \
|
||||
version_bump:$VERSION_BUMP \
|
||||
version_bump:skip \
|
||||
test_mode:true \
|
||||
--verbose
|
||||
else
|
||||
echo "🚀 Deploying to App Store Connect..."
|
||||
bundle exec fastlane ios deploy_auto \
|
||||
deployment_track:$DEPLOYMENT_TRACK \
|
||||
version_bump:$VERSION_BUMP \
|
||||
version_bump:skip \
|
||||
--verbose
|
||||
fi
|
||||
|
||||
- name: Verify iOS build output
|
||||
if: inputs.platform != 'android'
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
echo "🔍 Verifying iOS build artifacts..."
|
||||
|
||||
# Find the IPA file
|
||||
IPA_PATH=$(find ios/build -name "*.ipa" 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$IPA_PATH" ]; then
|
||||
echo "❌ ERROR: No IPA file found in ios/build directory"
|
||||
echo "Build may have failed silently. Check Fastlane logs above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Found IPA: $IPA_PATH"
|
||||
|
||||
# Check file size (should be at least 10MB for a real app)
|
||||
IPA_SIZE=$(stat -f%z "$IPA_PATH" 2>/dev/null || stat -c%s "$IPA_PATH")
|
||||
IPA_SIZE_MB=$((IPA_SIZE / 1024 / 1024))
|
||||
|
||||
echo "📦 IPA size: ${IPA_SIZE_MB}MB"
|
||||
|
||||
if [ "$IPA_SIZE" -lt 10485760 ]; then
|
||||
echo "⚠️ WARNING: IPA file is suspiciously small (< 10MB)"
|
||||
echo "This may indicate a build problem."
|
||||
fi
|
||||
|
||||
echo "✅ iOS build output verification passed"
|
||||
|
||||
# Version updates moved to separate job to avoid race conditions
|
||||
|
||||
- name: Remove project.pbxproj updates we don't want to commit
|
||||
@@ -624,8 +788,8 @@ jobs:
|
||||
echo "Node modules: $NODE_SIZE"
|
||||
fi
|
||||
|
||||
if [ -d "${{ env.APP_PATH }}/ios/vendor/bundle" ]; then
|
||||
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/ios/vendor/bundle" | cut -f1)
|
||||
if [ -d "${{ env.APP_PATH }}/vendor/bundle" ]; then
|
||||
GEMS_SIZE=$(du -sh "${{ env.APP_PATH }}/vendor/bundle" | cut -f1)
|
||||
echo "Ruby gems: $GEMS_SIZE"
|
||||
fi
|
||||
|
||||
@@ -638,6 +802,7 @@ jobs:
|
||||
echo "💡 GitHub Actions cache limit: 10GB per repository"
|
||||
|
||||
build-android:
|
||||
needs: [bump-version]
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
@@ -664,9 +829,8 @@ jobs:
|
||||
if: inputs.platform != 'ios'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# When triggered by PR merge, use the merge commit on staging
|
||||
# This ensures we deploy exactly what landed on staging (including version.json from source + any conflict resolutions)
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || 'staging' }}
|
||||
# Checkout the branch that triggered the workflow
|
||||
ref: ${{ github.ref_name }}
|
||||
- uses: "google-github-actions/auth@v2"
|
||||
with:
|
||||
project_id: "plucky-tempo-454713-r0"
|
||||
@@ -743,26 +907,6 @@ jobs:
|
||||
echo "NODE_VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
echo "NODE_VERSION_SANITIZED=${VERSION//\//-}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Determine version bump from PR labels or input
|
||||
id: version-bump
|
||||
run: |
|
||||
VERSION_BUMP="${{ inputs.version_bump || 'build' }}"
|
||||
|
||||
# Override with PR label if present
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
|
||||
if echo "$LABELS" | grep -q "version:major"; then
|
||||
VERSION_BUMP="major"
|
||||
elif echo "$LABELS" | grep -q "version:minor"; then
|
||||
VERSION_BUMP="minor"
|
||||
elif echo "$LABELS" | grep -q "version:patch"; then
|
||||
VERSION_BUMP="patch"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "version_bump=$VERSION_BUMP" >> $GITHUB_OUTPUT
|
||||
echo "📦 Version bump type: $VERSION_BUMP"
|
||||
|
||||
- name: Verify branch and commit (Android)
|
||||
if: inputs.platform != 'ios'
|
||||
run: |
|
||||
@@ -770,17 +914,33 @@ jobs:
|
||||
echo "Current branch: $(git branch --show-current || git symbolic-ref --short HEAD 2>/dev/null || echo 'detached')"
|
||||
echo "Current commit: $(git rev-parse HEAD)"
|
||||
echo "Current commit message: $(git log -1 --pretty=format:'%s')"
|
||||
echo "Staging HEAD commit: $(git rev-parse origin/staging)"
|
||||
echo "Staging HEAD message: $(git log -1 --pretty=format:'%s' origin/staging)"
|
||||
BUILD_BRANCH="${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }}"
|
||||
echo "Building from branch: $BUILD_BRANCH"
|
||||
echo "Target HEAD commit: $(git rev-parse origin/$BUILD_BRANCH)"
|
||||
echo "Target HEAD message: $(git log -1 --pretty=format:'%s' origin/$BUILD_BRANCH)"
|
||||
|
||||
if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/staging)" ]; then
|
||||
echo "⚠️ WARNING: Current commit differs from latest staging commit"
|
||||
echo "This might indicate we're not building from the latest staging branch"
|
||||
git log --oneline HEAD..origin/staging || true
|
||||
if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/$BUILD_BRANCH)" ]; then
|
||||
echo "⚠️ WARNING: Current commit differs from latest $BUILD_BRANCH commit"
|
||||
echo "This might indicate we're not building from the latest $BUILD_BRANCH branch"
|
||||
git log --oneline HEAD..origin/$BUILD_BRANCH || true
|
||||
else
|
||||
echo "✅ Building from latest staging commit"
|
||||
echo "✅ Building from latest $BUILD_BRANCH commit"
|
||||
fi
|
||||
|
||||
- name: Apply version bump for build
|
||||
if: inputs.platform != 'ios'
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
VERSION="${{ needs.bump-version.outputs.version }}"
|
||||
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
|
||||
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
|
||||
|
||||
echo "📝 Applying version bump for Android build: $VERSION (iOS Build: $IOS_BUILD, Android Build: $ANDROID_BUILD)"
|
||||
|
||||
# Use version-manager script to apply versions
|
||||
node ${{ env.APP_PATH }}/scripts/version-manager.cjs apply "$VERSION" "$IOS_BUILD" "$ANDROID_BUILD"
|
||||
|
||||
- name: Cache Yarn artifacts
|
||||
id: yarn-cache
|
||||
uses: ./.github/actions/cache-yarn
|
||||
@@ -795,7 +955,7 @@ jobs:
|
||||
id: gems-cache
|
||||
uses: ./.github/actions/cache-bundler
|
||||
with:
|
||||
path: ${{ env.APP_PATH }}/ios/vendor/bundle
|
||||
path: ${{ env.APP_PATH }}/vendor/bundle
|
||||
lock-file: app/Gemfile.lock
|
||||
cache-version: ${{ env.GH_CACHE_VERSION }}-${{ env.GH_GEMS_CACHE_VERSION }}-ruby${{ env.RUBY_VERSION }}
|
||||
|
||||
@@ -843,8 +1003,8 @@ jobs:
|
||||
|
||||
echo "✅ Lock files exist"
|
||||
|
||||
- name: Install Mobile Dependencies
|
||||
if: inputs.platform != 'ios'
|
||||
- name: Install Mobile Dependencies (main repo)
|
||||
if: inputs.platform != 'ios' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
uses: ./.github/actions/mobile-setup
|
||||
with:
|
||||
app_path: ${{ env.APP_PATH }}
|
||||
@@ -855,6 +1015,17 @@ jobs:
|
||||
SELFXYZ_INTERNAL_REPO_PAT: ${{ secrets.SELFXYZ_INTERNAL_REPO_PAT }}
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
|
||||
- name: Install Mobile Dependencies (forked PRs - no secrets)
|
||||
if: inputs.platform != 'ios' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
|
||||
uses: ./.github/actions/mobile-setup
|
||||
with:
|
||||
app_path: ${{ env.APP_PATH }}
|
||||
node_version: ${{ env.NODE_VERSION }}
|
||||
ruby_version: ${{ env.RUBY_VERSION }}
|
||||
workspace: ${{ env.WORKSPACE }}
|
||||
env:
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
|
||||
# android specific steps
|
||||
|
||||
- name: Setup Android SDK
|
||||
@@ -938,6 +1109,9 @@ jobs:
|
||||
- name: Build AAB with Fastlane
|
||||
if: inputs.platform != 'ios'
|
||||
env:
|
||||
CI_VERSION: ${{ needs.bump-version.outputs.version }}
|
||||
CI_IOS_BUILD: ${{ needs.bump-version.outputs.ios_build }}
|
||||
CI_ANDROID_BUILD: ${{ needs.bump-version.outputs.android_build }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }}
|
||||
@@ -957,20 +1131,53 @@ jobs:
|
||||
|
||||
# Determine deployment track and version bump
|
||||
DEPLOYMENT_TRACK="${{ inputs.deployment_track || 'internal' }}"
|
||||
VERSION_BUMP="${{ steps.version-bump.outputs.version_bump }}"
|
||||
VERSION_BUMP="${{ needs.bump-version.outputs.version_bump_type }}"
|
||||
TEST_MODE="${{ inputs.test_mode || false }}"
|
||||
|
||||
echo "🤖 Build Configuration:"
|
||||
echo " - Track: $DEPLOYMENT_TRACK"
|
||||
echo " - Version Bump: $VERSION_BUMP"
|
||||
echo " - Version Bump: $VERSION_BUMP (already applied in bump-version job)"
|
||||
echo " - Version: ${{ needs.bump-version.outputs.version }}"
|
||||
echo " - Android Build: ${{ needs.bump-version.outputs.android_build }}"
|
||||
echo " - Test Mode: $TEST_MODE"
|
||||
|
||||
echo "🔨 Building AAB with Fastlane..."
|
||||
bundle exec fastlane android build_only \
|
||||
deployment_track:$DEPLOYMENT_TRACK \
|
||||
version_bump:$VERSION_BUMP \
|
||||
version_bump:skip \
|
||||
--verbose
|
||||
|
||||
- name: Verify Android build output
|
||||
if: inputs.platform != 'ios'
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
echo "🔍 Verifying Android build artifacts..."
|
||||
|
||||
# Check for AAB file
|
||||
AAB_PATH="android/app/build/outputs/bundle/release/app-release.aab"
|
||||
|
||||
if [ ! -f "$AAB_PATH" ]; then
|
||||
echo "❌ ERROR: AAB file not found at $AAB_PATH"
|
||||
echo "Build may have failed silently. Check Fastlane logs above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Found AAB: $AAB_PATH"
|
||||
|
||||
# Check file size (should be at least 5MB for a real app)
|
||||
AAB_SIZE=$(stat -c%s "$AAB_PATH" 2>/dev/null || stat -f%z "$AAB_PATH")
|
||||
AAB_SIZE_MB=$((AAB_SIZE / 1024 / 1024))
|
||||
|
||||
echo "📦 AAB size: ${AAB_SIZE_MB}MB"
|
||||
|
||||
if [ "$AAB_SIZE" -lt 5242880 ]; then
|
||||
echo "⚠️ WARNING: AAB file is suspiciously small (< 5MB)"
|
||||
echo "This may indicate a build problem."
|
||||
fi
|
||||
|
||||
echo "✅ Android build output verification passed"
|
||||
|
||||
- name: Upload to Google Play Store using WIF
|
||||
if: inputs.platform != 'ios' && inputs.test_mode != true
|
||||
timeout-minutes: 10
|
||||
@@ -1016,9 +1223,12 @@ jobs:
|
||||
echo "💡 GitHub Actions cache limit: 10GB per repository"
|
||||
|
||||
# Consolidated version bump PR - runs after both platforms complete
|
||||
# NOTE: This job checks out the TARGET branch for version bump (default: dev)
|
||||
# This is DIFFERENT from the build branch - we build from staging/feature branch,
|
||||
# but create the version bump PR to dev so it can be reviewed before merging to staging
|
||||
create-version-bump-pr:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-ios, build-android]
|
||||
needs: [bump-version, build-ios, build-android]
|
||||
if: |
|
||||
always() &&
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.merged == true) &&
|
||||
@@ -1029,46 +1239,145 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Checkout staging where the builds ran
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || 'staging' }}
|
||||
# Checkout target branch for version bump PR (default: dev, override with bump_target_branch input)
|
||||
ref: ${{ inputs.bump_target_branch || 'dev' }}
|
||||
|
||||
- name: Get version from package.json
|
||||
id: get-version
|
||||
uses: ./.github/actions/get-version
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
app_path: ${{ env.APP_PATH }}
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Determine platforms that succeeded
|
||||
- name: Apply version bump from outputs
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
VERSION="${{ needs.bump-version.outputs.version }}"
|
||||
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
|
||||
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
|
||||
|
||||
echo "📝 Applying version bump: $VERSION (iOS: $IOS_BUILD, Android: $ANDROID_BUILD)"
|
||||
|
||||
# Update package.json version
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
pkg.version = '$VERSION';
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('✅ Updated package.json');
|
||||
"
|
||||
|
||||
# Update version.json build numbers
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const version = JSON.parse(fs.readFileSync('version.json', 'utf8'));
|
||||
version.ios.build = $IOS_BUILD;
|
||||
version.android.build = $ANDROID_BUILD;
|
||||
fs.writeFileSync('version.json', JSON.stringify(version, null, 2) + '\n');
|
||||
console.log('✅ Updated version.json');
|
||||
"
|
||||
|
||||
echo "✅ Versions applied successfully"
|
||||
|
||||
- name: Determine platforms that succeeded and PR title
|
||||
id: platforms
|
||||
run: |
|
||||
PLATFORMS=""
|
||||
if [ "${{ needs.build-ios.result }}" = "success" ]; then
|
||||
PLATFORMS="${PLATFORMS}iOS "
|
||||
fi
|
||||
if [ "${{ needs.build-android.result }}" = "success" ]; then
|
||||
PLATFORMS="${PLATFORMS}Android"
|
||||
fi
|
||||
echo "platforms=${PLATFORMS}" >> $GITHUB_OUTPUT
|
||||
echo "📱 Successful builds: $PLATFORMS"
|
||||
VERSION="${{ needs.bump-version.outputs.version }}"
|
||||
PLATFORM="${{ needs.bump-version.outputs.platform }}"
|
||||
IOS_RESULT="${{ needs.build-ios.result }}"
|
||||
ANDROID_RESULT="${{ needs.build-android.result }}"
|
||||
|
||||
- name: Create consolidated version bump PR
|
||||
uses: ./.github/actions/create-version-bump-pr
|
||||
with:
|
||||
platform: mobile
|
||||
version: ${{ steps.get-version.outputs.version }}
|
||||
file_paths: |
|
||||
app/version.json
|
||||
app/package.json
|
||||
app/ios/Self.xcodeproj/project.pbxproj
|
||||
app/ios/OpenPassport/Info.plist
|
||||
app/android/app/build.gradle
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Determine what was actually built
|
||||
PLATFORMS_BUILT=""
|
||||
if [ "$IOS_RESULT" = "success" ]; then
|
||||
PLATFORMS_BUILT="iOS"
|
||||
fi
|
||||
if [ "$ANDROID_RESULT" = "success" ]; then
|
||||
if [ -n "$PLATFORMS_BUILT" ]; then
|
||||
PLATFORMS_BUILT="${PLATFORMS_BUILT} & Android"
|
||||
else
|
||||
PLATFORMS_BUILT="Android"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate PR title based on what was bumped
|
||||
if [ "$PLATFORM" = "ios" ]; then
|
||||
PR_TITLE="chore: bump iOS version to $VERSION"
|
||||
elif [ "$PLATFORM" = "android" ]; then
|
||||
PR_TITLE="chore: bump Android version to $VERSION"
|
||||
else
|
||||
PR_TITLE="chore: bump mobile app version to $VERSION"
|
||||
fi
|
||||
|
||||
echo "platforms=${PLATFORMS_BUILT}" >> $GITHUB_OUTPUT
|
||||
echo "pr_title=${PR_TITLE}" >> $GITHUB_OUTPUT
|
||||
echo "📱 Successful builds: $PLATFORMS_BUILT"
|
||||
echo "📝 PR title: $PR_TITLE"
|
||||
|
||||
- name: Create version bump PR
|
||||
if: inputs.dry_run != true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION="${{ needs.bump-version.outputs.version }}"
|
||||
TARGET_BRANCH="${{ inputs.bump_target_branch || 'dev' }}"
|
||||
# Use version-based branch name for idempotency
|
||||
BRANCH_NAME="ci/bump-mobile-version-${VERSION}"
|
||||
PR_TITLE="${{ steps.platforms.outputs.pr_title }}"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Check if branch already exists (idempotent PR creation)
|
||||
if git ls-remote --heads origin "${BRANCH_NAME}" | grep -q "${BRANCH_NAME}"; then
|
||||
echo "⚠️ Branch ${BRANCH_NAME} already exists"
|
||||
echo "ℹ️ Version bump PR may already exist for version ${VERSION}"
|
||||
echo "ℹ️ Skipping PR creation to avoid duplicates"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Commit the version changes
|
||||
cd ${{ env.APP_PATH }}
|
||||
git add package.json version.json
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "⚠️ No version changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "chore: bump mobile app version to $VERSION [skip ci]"
|
||||
|
||||
# Create new branch from current HEAD (bump target branch with version bump)
|
||||
git checkout -b ${BRANCH_NAME}
|
||||
|
||||
# Push the branch
|
||||
git push --set-upstream origin ${BRANCH_NAME}
|
||||
|
||||
# Create PR to target branch (usually dev)
|
||||
echo "Creating PR to ${TARGET_BRANCH}..."
|
||||
gh pr create \
|
||||
--base ${TARGET_BRANCH} \
|
||||
--head ${BRANCH_NAME} \
|
||||
--title "${PR_TITLE}" \
|
||||
--body "🤖 Automated version bump after successful deployment
|
||||
|
||||
**Version:** $VERSION
|
||||
**iOS Build:** ${{ needs.bump-version.outputs.ios_build }}
|
||||
**Android Build:** ${{ needs.bump-version.outputs.android_build }}
|
||||
**Platforms Built:** ${{ steps.platforms.outputs.platforms }}
|
||||
**Build Branch:** ${{ github.ref_name }}
|
||||
**Target Branch:** ${TARGET_BRANCH}
|
||||
|
||||
This PR was automatically created by the mobile deployment workflow." \
|
||||
--label "automated"
|
||||
|
||||
echo "✅ Version bump PR created successfully to ${TARGET_BRANCH}"
|
||||
|
||||
# Create git tags after successful deployment
|
||||
create-release-tags:
|
||||
needs: [build-ios, build-android, create-version-bump-pr]
|
||||
needs: [bump-version, build-ios, build-android, create-version-bump-pr]
|
||||
if: |
|
||||
always() &&
|
||||
(inputs.dry_run != true) &&
|
||||
needs.create-version-bump-pr.result == 'success' &&
|
||||
(needs.build-ios.result == 'success' || needs.build-android.result == 'success') &&
|
||||
(inputs.deployment_track == 'production')
|
||||
@@ -1077,9 +1386,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# When triggered by PR merge, use the merge commit on staging
|
||||
# This ensures we tag exactly what landed on staging (including version.json from source + any conflict resolutions)
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha || 'staging' }}
|
||||
# Checkout target branch for tagging (usually dev)
|
||||
ref: ${{ inputs.bump_target_branch || 'dev' }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Configure Git
|
||||
@@ -1091,47 +1399,75 @@ jobs:
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
# Read current version info
|
||||
VERSION=$(cat package.json | jq -r .version)
|
||||
IOS_BUILD=$(cat version.json | jq -r .ios.build)
|
||||
ANDROID_BUILD=$(cat version.json | jq -r .android.build)
|
||||
# Use version info from bump-version outputs
|
||||
VERSION="${{ needs.bump-version.outputs.version }}"
|
||||
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
|
||||
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
|
||||
|
||||
echo "📦 Creating tags for version $VERSION"
|
||||
|
||||
# Create main version tag
|
||||
if ! git tag -l | grep -q "^v${VERSION}$"; then
|
||||
git tag -a "v${VERSION}" -m "Release ${VERSION}"
|
||||
# Create main version tag (idempotent)
|
||||
if git tag -a "v${VERSION}" -m "Release ${VERSION}" 2>/dev/null; then
|
||||
echo "✅ Created tag: v${VERSION}"
|
||||
else
|
||||
echo "⏭️ Tag v${VERSION} already exists"
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 128 ]; then
|
||||
echo "⏭️ Tag v${VERSION} already exists"
|
||||
else
|
||||
echo "❌ Failed to create tag v${VERSION} with exit code $EXIT_CODE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create platform-specific tags if deployments succeeded
|
||||
# Create platform-specific tags if deployments succeeded (idempotent)
|
||||
if [ "${{ needs.build-ios.result }}" = "success" ]; then
|
||||
TAG_NAME="v${VERSION}-ios-${IOS_BUILD}"
|
||||
if ! git tag -l | grep -q "^${TAG_NAME}$"; then
|
||||
git tag -a "${TAG_NAME}" -m "iOS Release ${VERSION} (Build ${IOS_BUILD})"
|
||||
if git tag -a "${TAG_NAME}" -m "iOS Release ${VERSION} (Build ${IOS_BUILD})" 2>/dev/null; then
|
||||
echo "✅ Created tag: ${TAG_NAME}"
|
||||
else
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 128 ]; then
|
||||
echo "⏭️ Tag ${TAG_NAME} already exists"
|
||||
else
|
||||
echo "❌ Failed to create tag ${TAG_NAME} with exit code $EXIT_CODE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${{ needs.build-android.result }}" = "success" ]; then
|
||||
TAG_NAME="v${VERSION}-android-${ANDROID_BUILD}"
|
||||
if ! git tag -l | grep -q "^${TAG_NAME}$"; then
|
||||
git tag -a "${TAG_NAME}" -m "Android Release ${VERSION} (Build ${ANDROID_BUILD})"
|
||||
if git tag -a "${TAG_NAME}" -m "Android Release ${VERSION} (Build ${ANDROID_BUILD})" 2>/dev/null; then
|
||||
echo "✅ Created tag: ${TAG_NAME}"
|
||||
else
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 128 ]; then
|
||||
echo "⏭️ Tag ${TAG_NAME} already exists"
|
||||
else
|
||||
echo "❌ Failed to create tag ${TAG_NAME} with exit code $EXIT_CODE"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Push all tags
|
||||
git push origin --tags
|
||||
echo "🚀 Tags pushed to repository"
|
||||
# Push all tags (force to handle any conflicts)
|
||||
if git push origin --tags 2>/dev/null; then
|
||||
echo "🚀 Tags pushed to repository"
|
||||
else
|
||||
echo "⚠️ Some tags may already exist on remote, trying force push..."
|
||||
git push origin --tags --force
|
||||
echo "🚀 Tags force-pushed to repository"
|
||||
fi
|
||||
|
||||
- name: Generate changelog for release
|
||||
id: changelog
|
||||
run: |
|
||||
cd ${{ env.APP_PATH }}
|
||||
|
||||
VERSION="${{ needs.bump-version.outputs.version }}"
|
||||
IOS_BUILD="${{ needs.bump-version.outputs.ios_build }}"
|
||||
ANDROID_BUILD="${{ needs.bump-version.outputs.android_build }}"
|
||||
|
||||
# Find the previous version tag
|
||||
PREV_TAG=$(git tag -l "v*" | grep -v "-" | sort -V | tail -2 | head -1 || echo "")
|
||||
|
||||
|
||||
4
.github/workflows/mobile-e2e.yml
vendored
4
.github/workflows/mobile-e2e.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-android-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 120
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
|
||||
e2e-ios:
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 120
|
||||
runs-on: macos-latest-large
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-ios-${{ github.ref }}
|
||||
|
||||
@@ -95,27 +95,14 @@ platform :ios do
|
||||
UI.message(" Version bump: #{version_bump}")
|
||||
UI.message(" Test mode: #{test_mode}")
|
||||
|
||||
# Handle version bumping
|
||||
if !test_mode
|
||||
require_relative "helpers/version_manager"
|
||||
|
||||
case version_bump
|
||||
when "major", "minor", "patch"
|
||||
# Use Node.js with semver to bump version
|
||||
sh("cd .. && node -e \"const fs = require('fs'); const pkg = require('./package.json'); const semver = require('semver'); pkg.version = semver.inc(pkg.version, '#{version_bump}'); fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\\n');\"")
|
||||
UI.success("✅ Bumped #{version_bump} version")
|
||||
|
||||
# Sync the new version to iOS project files
|
||||
sync_version
|
||||
UI.success("✅ Synced MARKETING_VERSION to iOS project")
|
||||
when "build"
|
||||
# Build number is handled in prepare_ios_build
|
||||
UI.message("📦 Build number will be incremented during build")
|
||||
end
|
||||
# In CI, version should already be set by version-manager.cjs
|
||||
# Verify it matches expected values
|
||||
if is_ci && version_bump == "skip"
|
||||
Fastlane::Helpers.verify_ci_version_match
|
||||
end
|
||||
|
||||
# Prepare and build
|
||||
result = prepare_ios_build(prod_release: deployment_track == "production")
|
||||
# Prepare and build (no version bumping inside)
|
||||
result = prepare_ios_build(prod_release: deployment_track == "production", version_bump: version_bump)
|
||||
|
||||
# Handle deployment based on track
|
||||
if test_mode
|
||||
@@ -146,6 +133,8 @@ platform :ios do
|
||||
end
|
||||
|
||||
private_lane :prepare_ios_build do |options|
|
||||
version_bump = options[:version_bump] || "build"
|
||||
|
||||
if local_development
|
||||
# app breaks with Xcode 16.3
|
||||
xcode_select "/Applications/Xcode.app"
|
||||
@@ -183,10 +172,11 @@ platform :ios do
|
||||
|
||||
Fastlane::Helpers.verify_env_vars(required_env_vars)
|
||||
|
||||
# Get build number from version.json and increment it
|
||||
build_number = Fastlane::Helpers.bump_ios_build_number
|
||||
# Read build number from version.json (already set by CI or local version-manager.cjs)
|
||||
build_number = Fastlane::Helpers.get_ios_build_number
|
||||
UI.message("📦 Using iOS build number: #{build_number}")
|
||||
|
||||
# Update Xcode project with new build number
|
||||
# Update Xcode project with build number
|
||||
increment_build_number(
|
||||
build_number: build_number,
|
||||
xcodeproj: "ios/#{ENV["IOS_PROJECT_NAME"]}.xcodeproj",
|
||||
@@ -234,7 +224,7 @@ platform :ios do
|
||||
workspace: "#{workspace_path}",
|
||||
scheme: PROJECT_SCHEME,
|
||||
export_method: "app-store",
|
||||
output_directory: "build",
|
||||
output_directory: "ios/build",
|
||||
clean: true,
|
||||
export_options: {
|
||||
method: "app-store",
|
||||
@@ -298,37 +288,24 @@ platform :android do
|
||||
UI.message(" Version bump: #{version_bump}")
|
||||
UI.message(" Test mode: #{test_mode}")
|
||||
|
||||
# Handle version bumping
|
||||
if !test_mode
|
||||
require_relative "helpers/version_manager"
|
||||
|
||||
case version_bump
|
||||
when "major", "minor", "patch"
|
||||
# Use Node.js with semver to bump version
|
||||
sh("cd .. && node -e \"const fs = require('fs'); const pkg = require('./package.json'); const semver = require('semver'); pkg.version = semver.inc(pkg.version, '#{version_bump}'); fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\\n');\"")
|
||||
UI.success("✅ Bumped #{version_bump} version")
|
||||
# Get the new version and sync to build.gradle
|
||||
new_version = Fastlane::Helpers::VersionManager.get_current_version
|
||||
android_set_version_name(
|
||||
version_name: new_version,
|
||||
gradle_file: android_gradle_file_path.gsub("../", ""),
|
||||
)
|
||||
when "build"
|
||||
# Build number is automatically incremented during build
|
||||
UI.message("📦 Build number will be incremented")
|
||||
end
|
||||
# In CI, version should already be set by version-manager.cjs
|
||||
# Verify it matches expected values
|
||||
if is_ci && version_bump == "skip"
|
||||
Fastlane::Helpers.verify_ci_version_match
|
||||
end
|
||||
|
||||
# Map deployment track to Play Store track
|
||||
play_store_track = deployment_track == "production" ? "production" : "internal"
|
||||
|
||||
# Build and deploy
|
||||
upload_android_build(track: play_store_track, test_mode: test_mode, deployment_track: deployment_track)
|
||||
upload_android_build(track: play_store_track, test_mode: test_mode, deployment_track: deployment_track, version_bump: version_bump)
|
||||
end
|
||||
|
||||
private_lane :upload_android_build do |options|
|
||||
test_mode = options[:test_mode] == true || options[:test_mode] == "true"
|
||||
skip_upload = options[:skip_upload] == true || options[:skip_upload] == "true"
|
||||
version_bump = options[:version_bump] || "build"
|
||||
|
||||
if local_development
|
||||
if ENV["ANDROID_KEYSTORE_PATH"].nil?
|
||||
ENV["ANDROID_KEYSTORE_PATH"] = Fastlane::Helpers.android_create_keystore(android_keystore_path)
|
||||
@@ -352,10 +329,11 @@ platform :android do
|
||||
|
||||
Fastlane::Helpers.verify_env_vars(required_env_vars)
|
||||
|
||||
# Get version code from version.json and increment it
|
||||
version_code = Fastlane::Helpers.bump_android_build_number
|
||||
# Read version code from version.json (already set by CI or local version-manager.cjs)
|
||||
version_code = Fastlane::Helpers.get_android_build_number
|
||||
UI.message("📦 Using Android build number: #{version_code}")
|
||||
|
||||
# Update build.gradle with new version code
|
||||
# Update build.gradle with version code
|
||||
increment_version_code(
|
||||
version_code: version_code,
|
||||
gradle_file_path: android_gradle_file_path.gsub("../", ""),
|
||||
|
||||
@@ -44,22 +44,37 @@ module Fastlane
|
||||
data["android"]["build"]
|
||||
end
|
||||
|
||||
def bump_ios_build_number
|
||||
data = read_version_file
|
||||
current = data["ios"]["build"]
|
||||
data["ios"]["build"] = current + 1
|
||||
write_version_file(data)
|
||||
UI.success("iOS build number bumped from #{current} to #{data["ios"]["build"]}")
|
||||
data["ios"]["build"]
|
||||
end
|
||||
def verify_ci_version_match
|
||||
# Verify that versions were pre-set by CI
|
||||
unless ENV["CI_VERSION"] && ENV["CI_IOS_BUILD"] && ENV["CI_ANDROID_BUILD"]
|
||||
UI.user_error!("CI must set CI_VERSION, CI_IOS_BUILD, and CI_ANDROID_BUILD environment variables")
|
||||
end
|
||||
|
||||
def bump_android_build_number
|
||||
data = read_version_file
|
||||
current = data["android"]["build"]
|
||||
data["android"]["build"] = current + 1
|
||||
write_version_file(data)
|
||||
UI.success("Android build number bumped from #{current} to #{data["android"]["build"]}")
|
||||
data["android"]["build"]
|
||||
pkg_version = get_current_version
|
||||
ios_build = get_ios_build_number
|
||||
android_build = get_android_build_number
|
||||
|
||||
expected_version = ENV["CI_VERSION"]
|
||||
expected_ios_build = ENV["CI_IOS_BUILD"].to_i
|
||||
expected_android_build = ENV["CI_ANDROID_BUILD"].to_i
|
||||
|
||||
version_matches = pkg_version == expected_version
|
||||
ios_matches = ios_build == expected_ios_build
|
||||
android_matches = android_build == expected_android_build
|
||||
|
||||
unless version_matches && ios_matches && android_matches
|
||||
UI.error("Version mismatch detected!")
|
||||
UI.error("Expected: v#{expected_version} (iOS: #{expected_ios_build}, Android: #{expected_android_build})")
|
||||
UI.error("Actual: v#{pkg_version} (iOS: #{ios_build}, Android: #{android_build})")
|
||||
UI.user_error!("Version mismatch! CI version-manager script should have set these correctly.")
|
||||
end
|
||||
|
||||
UI.success("✅ Version verification passed:")
|
||||
UI.message(" Version: #{pkg_version}")
|
||||
UI.message(" iOS Build: #{ios_build}")
|
||||
UI.message(" Android Build: #{android_build}")
|
||||
|
||||
{ version: pkg_version, ios_build: ios_build, android_build: android_build }
|
||||
end
|
||||
|
||||
def update_deployment_timestamp(platform)
|
||||
|
||||
329
app/scripts/version-manager.cjs
Executable file
329
app/scripts/version-manager.cjs
Executable file
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.
|
||||
|
||||
/**
|
||||
* Centralized Version Manager for Mobile Deployments
|
||||
*
|
||||
* Single source of truth for all version operations across:
|
||||
* - GitHub Actions workflows
|
||||
* - Fastlane (read-only consumption)
|
||||
* - Local development
|
||||
*
|
||||
* Version Bump Behavior (Option B - Continue build numbers):
|
||||
* - major: 2.6.9 → 3.0.0, increment build numbers
|
||||
* - minor: 2.6.9 → 2.7.0, increment build numbers
|
||||
* - patch: 2.6.9 → 2.6.10, increment build numbers
|
||||
* - build: 2.6.9 → 2.6.9, increment build numbers only
|
||||
*
|
||||
* Platform-specific logic:
|
||||
* - ios: Only increment iOS build number
|
||||
* - android: Only increment Android build number
|
||||
* - both/undefined: Increment both build numbers
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const APP_DIR = path.resolve(__dirname, '..');
|
||||
const PACKAGE_JSON_PATH = path.join(APP_DIR, 'package.json');
|
||||
const VERSION_JSON_PATH = path.join(APP_DIR, 'version.json');
|
||||
|
||||
/**
|
||||
* Read package.json
|
||||
*/
|
||||
function readPackageJson() {
|
||||
if (!fs.existsSync(PACKAGE_JSON_PATH)) {
|
||||
throw new Error(`package.json not found at ${PACKAGE_JSON_PATH}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8'));
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse package.json: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read version.json
|
||||
*/
|
||||
function readVersionJson() {
|
||||
if (!fs.existsSync(VERSION_JSON_PATH)) {
|
||||
throw new Error(`version.json not found at ${VERSION_JSON_PATH}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(VERSION_JSON_PATH, 'utf8'));
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse version.json: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write package.json
|
||||
*/
|
||||
function writePackageJson(data) {
|
||||
try {
|
||||
fs.writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(data, null, 2) + '\n');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to write package.json: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write version.json
|
||||
*/
|
||||
function writeVersionJson(data) {
|
||||
try {
|
||||
fs.writeFileSync(VERSION_JSON_PATH, JSON.stringify(data, null, 2) + '\n');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to write version.json: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current version information
|
||||
*/
|
||||
function getVersionInfo() {
|
||||
const pkg = readPackageJson();
|
||||
const versionData = readVersionJson();
|
||||
|
||||
return {
|
||||
version: pkg.version,
|
||||
iosBuild: versionData.ios.build,
|
||||
androidBuild: versionData.android.build,
|
||||
iosLastDeployed: versionData.ios.lastDeployed,
|
||||
androidLastDeployed: versionData.android.lastDeployed,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Bump semantic version (major/minor/patch)
|
||||
*/
|
||||
function bumpSemanticVersion(currentVersion, bumpType) {
|
||||
const parts = currentVersion.split('.').map(Number);
|
||||
|
||||
if (parts.length !== 3 || parts.some(isNaN)) {
|
||||
throw new Error(
|
||||
`Invalid version format: ${currentVersion}. Expected X.Y.Z`,
|
||||
);
|
||||
}
|
||||
|
||||
let [major, minor, patch] = parts;
|
||||
|
||||
switch (bumpType) {
|
||||
case 'major':
|
||||
major += 1;
|
||||
minor = 0;
|
||||
patch = 0;
|
||||
break;
|
||||
case 'minor':
|
||||
minor += 1;
|
||||
patch = 0;
|
||||
break;
|
||||
case 'patch':
|
||||
patch += 1;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid bump type: ${bumpType}. Expected major, minor, or patch`,
|
||||
);
|
||||
}
|
||||
|
||||
return `${major}.${minor}.${patch}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bump version and build numbers
|
||||
*
|
||||
* @param {string} bumpType - 'major', 'minor', 'patch', or 'build'
|
||||
* @param {string} platform - 'ios', 'android', or 'both' (default)
|
||||
* @returns {object} - New version info
|
||||
*/
|
||||
function bumpVersion(bumpType, platform = 'both') {
|
||||
const validBumpTypes = ['major', 'minor', 'patch', 'build'];
|
||||
const validPlatforms = ['ios', 'android', 'both'];
|
||||
|
||||
if (!validBumpTypes.includes(bumpType)) {
|
||||
throw new Error(
|
||||
`Invalid bump type: ${bumpType}. Expected: ${validBumpTypes.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!validPlatforms.includes(platform)) {
|
||||
throw new Error(
|
||||
`Invalid platform: ${platform}. Expected: ${validPlatforms.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const pkg = readPackageJson();
|
||||
const versionData = readVersionJson();
|
||||
|
||||
let newVersion = pkg.version;
|
||||
|
||||
// Bump semantic version if major/minor/patch
|
||||
if (bumpType !== 'build') {
|
||||
newVersion = bumpSemanticVersion(pkg.version, bumpType);
|
||||
console.log(
|
||||
`📦 Bumping ${bumpType} version: ${pkg.version} → ${newVersion}`,
|
||||
);
|
||||
} else {
|
||||
console.log(`📦 Keeping version: ${newVersion} (build-only bump)`);
|
||||
}
|
||||
|
||||
// Bump build numbers based on platform
|
||||
let newIosBuild = versionData.ios.build;
|
||||
let newAndroidBuild = versionData.android.build;
|
||||
|
||||
if (platform === 'ios' || platform === 'both') {
|
||||
newIosBuild += 1;
|
||||
console.log(`🍎 iOS build: ${versionData.ios.build} → ${newIosBuild}`);
|
||||
} else {
|
||||
console.log(`🍎 iOS build: ${newIosBuild} (unchanged)`);
|
||||
}
|
||||
|
||||
if (platform === 'android' || platform === 'both') {
|
||||
newAndroidBuild += 1;
|
||||
console.log(
|
||||
`🤖 Android build: ${versionData.android.build} → ${newAndroidBuild}`,
|
||||
);
|
||||
} else {
|
||||
console.log(`🤖 Android build: ${newAndroidBuild} (unchanged)`);
|
||||
}
|
||||
|
||||
return {
|
||||
version: newVersion,
|
||||
iosBuild: newIosBuild,
|
||||
androidBuild: newAndroidBuild,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply version changes to files
|
||||
*/
|
||||
function applyVersions(version, iosBuild, androidBuild) {
|
||||
console.log(`📝 Applying versions to files...`);
|
||||
console.log(` Version: ${version}`);
|
||||
console.log(` iOS Build: ${iosBuild}`);
|
||||
console.log(` Android Build: ${androidBuild}`);
|
||||
|
||||
// Update package.json
|
||||
const pkg = readPackageJson();
|
||||
pkg.version = version;
|
||||
writePackageJson(pkg);
|
||||
console.log(`✅ Updated package.json`);
|
||||
|
||||
// Update version.json
|
||||
const versionData = readVersionJson();
|
||||
versionData.ios.build = iosBuild;
|
||||
versionData.android.build = androidBuild;
|
||||
writeVersionJson(versionData);
|
||||
console.log(`✅ Updated version.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI Interface
|
||||
*/
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const command = args[0];
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 'get': {
|
||||
// Get current version info
|
||||
const info = getVersionInfo();
|
||||
console.log(JSON.stringify(info, null, 2));
|
||||
|
||||
// Also output for GitHub Actions
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
const output = [
|
||||
`version=${info.version}`,
|
||||
`ios_build=${info.iosBuild}`,
|
||||
`android_build=${info.androidBuild}`,
|
||||
].join('\n');
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, output + '\n');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'bump': {
|
||||
// Bump version: bump <type> <platform>
|
||||
const bumpType = args[1] || 'build';
|
||||
const platform = args[2] || 'both';
|
||||
|
||||
const result = bumpVersion(bumpType, platform);
|
||||
console.log(`\n✅ Version bump calculated:`);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
// Output for GitHub Actions
|
||||
if (process.env.GITHUB_OUTPUT) {
|
||||
const output = [
|
||||
`version=${result.version}`,
|
||||
`ios_build=${result.iosBuild}`,
|
||||
`android_build=${result.androidBuild}`,
|
||||
].join('\n');
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, output + '\n');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'apply': {
|
||||
// Apply version: apply <version> <iosBuild> <androidBuild>
|
||||
const version = args[1];
|
||||
const iosBuild = parseInt(args[2], 10);
|
||||
const androidBuild = parseInt(args[3], 10);
|
||||
|
||||
if (!version || isNaN(iosBuild) || isNaN(androidBuild)) {
|
||||
throw new Error('Usage: apply <version> <iosBuild> <androidBuild>');
|
||||
}
|
||||
|
||||
applyVersions(version, iosBuild, androidBuild);
|
||||
console.log(`\n✅ Versions applied successfully`);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.log(`
|
||||
Mobile Version Manager
|
||||
|
||||
Usage:
|
||||
node version-manager.cjs <command> [options]
|
||||
|
||||
Commands:
|
||||
get Get current version information
|
||||
bump <type> <platform> Bump version and calculate new build numbers
|
||||
type: major|minor|patch|build (default: build)
|
||||
platform: ios|android|both (default: both)
|
||||
apply <version> <ios> <android> Apply specific version and build numbers
|
||||
|
||||
Examples:
|
||||
node version-manager.cjs get
|
||||
node version-manager.cjs bump build both
|
||||
node version-manager.cjs bump patch ios
|
||||
node version-manager.cjs apply 2.7.0 180 109
|
||||
`);
|
||||
process.exit(command ? 1 : 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run CLI if called directly
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
// Export functions for use as module
|
||||
module.exports = {
|
||||
applyVersions,
|
||||
bumpVersion,
|
||||
getVersionInfo,
|
||||
readPackageJson,
|
||||
readVersionJson,
|
||||
};
|
||||
Reference in New Issue
Block a user