iOS Build using GH Actions
Build & Push Docker Images / test-backend (push) Successful in 21s
Build & Push Docker Images / test-frontend (push) Successful in 22s
Build & Push Docker Images / build-backend (push) Successful in 16s
Build & Push Docker Images / build-frontend (push) Successful in 17s
Build & Push Docker Images / test-backend (push) Successful in 21s
Build & Push Docker Images / test-frontend (push) Successful in 22s
Build & Push Docker Images / build-backend (push) Successful in 16s
Build & Push Docker Images / build-frontend (push) Successful in 17s
This commit is contained in:
@@ -0,0 +1,166 @@
|
|||||||
|
name: iOS Build & Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "ios-v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
upload_to_testflight:
|
||||||
|
description: "Upload to TestFlight after build"
|
||||||
|
required: false
|
||||||
|
default: "true"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- "true"
|
||||||
|
- "false"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ios-build-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install backend dependencies & test
|
||||||
|
run: cd backend && npm ci && npm test -- --ci
|
||||||
|
|
||||||
|
- name: Install frontend dependencies & test
|
||||||
|
run: cd frontend && npm ci && npm test -- --run --reporter=basic
|
||||||
|
|
||||||
|
build-ios:
|
||||||
|
name: Build iOS App
|
||||||
|
needs: test
|
||||||
|
runs-on: macos-14
|
||||||
|
env:
|
||||||
|
SCHEME: App
|
||||||
|
PROJECT: ios/App/App.xcodeproj
|
||||||
|
WORKSPACE: ios/App/App.xcworkspace
|
||||||
|
ARCHIVE_PATH: build/App.xcarchive
|
||||||
|
IPA_PATH: build/App.ipa
|
||||||
|
EXPORT_OPTIONS: scripts/export-options.plist
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install root dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: cd frontend && npm ci
|
||||||
|
|
||||||
|
- name: Build web bundle
|
||||||
|
run: npm run build:web
|
||||||
|
|
||||||
|
- name: Ensure iOS platform & sync Capacitor
|
||||||
|
run: |
|
||||||
|
if [ ! -d "ios/App/App.xcodeproj" ]; then
|
||||||
|
echo "Adding iOS platform..."
|
||||||
|
npx cap add ios
|
||||||
|
fi
|
||||||
|
npx cap sync ios
|
||||||
|
|
||||||
|
- name: Install CocoaPods
|
||||||
|
run: cd ios/App && pod install --repo-update
|
||||||
|
|
||||||
|
- name: Install Apple certificates & provisioning profile
|
||||||
|
uses: apple-actions/import-codesign-certs@v2
|
||||||
|
with:
|
||||||
|
p12-file-base64: ${{ secrets.IOS_P12_BASE64 }}
|
||||||
|
p12-password: ${{ secrets.IOS_P12_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Install provisioning profile
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
|
echo "${{ secrets.IOS_PROVISION_PROFILE_BASE64 }}" | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/${{ secrets.IOS_PROVISION_PROFILE_UUID }}.mobileprovision
|
||||||
|
|
||||||
|
- name: Set version from tag
|
||||||
|
if: startsWith(github.ref, 'refs/tags/ios-v')
|
||||||
|
run: |
|
||||||
|
TAG_VERSION="${GITHUB_REF#refs/tags/ios-v}"
|
||||||
|
MAJOR=$(echo "$TAG_VERSION" | cut -d. -f1)
|
||||||
|
MINOR=$(echo "$TAG_VERSION" | cut -d. -f2)
|
||||||
|
PATCH=$(echo "$TAG_VERSION" | cut -d. -f3)
|
||||||
|
BUILD=${{ github.run_number }}
|
||||||
|
|
||||||
|
echo "Setting MARKETING_VERSION=$MAJOR.$MINOR.$PATCH"
|
||||||
|
echo "Setting CURRENT_PROJECT_VERSION=$BUILD"
|
||||||
|
|
||||||
|
sed -i '' "s/MARKETING_VERSION = .*;/MARKETING_VERSION = $MAJOR.$MINOR.$PATCH;/" ios/App/App.xcodeproj/project.pbxproj
|
||||||
|
sed -i '' "s/CURRENT_PROJECT_VERSION = .*;/CURRENT_PROJECT_VERSION = $BUILD;/" ios/App/App.xcodeproj/project.pbxproj
|
||||||
|
|
||||||
|
- name: Build Xcode archive
|
||||||
|
run: |
|
||||||
|
xcodebuild archive \
|
||||||
|
-workspace "$WORKSPACE" \
|
||||||
|
-scheme "$SCHEME" \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "generic/platform=iOS" \
|
||||||
|
-archivePath "$ARCHIVE_PATH" \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
CODE_SIGN_STYLE=Manual \
|
||||||
|
DEVELOPMENT_TEAM="${{ secrets.IOS_TEAM_ID }}" \
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER="${{ secrets.IOS_PROVISION_PROFILE_NAME }}" \
|
||||||
|
| xcpretty --color || true
|
||||||
|
|
||||||
|
- name: Export IPA
|
||||||
|
run: |
|
||||||
|
plutil -replace teamID -string "${{ secrets.IOS_TEAM_ID }}" "$EXPORT_OPTIONS" 2>/dev/null || true
|
||||||
|
plutil -replace provisioningProfiles.net.hacecalor.farmafinder -string "${{ secrets.IOS_PROVISION_PROFILE_NAME }}" "$EXPORT_OPTIONS" 2>/dev/null || true
|
||||||
|
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "$ARCHIVE_PATH" \
|
||||||
|
-exportOptionsPlist "$EXPORT_OPTIONS" \
|
||||||
|
-exportPath build \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
| xcpretty --color || true
|
||||||
|
|
||||||
|
- name: Upload artifact (IPA)
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: FarmaFinder-iOS
|
||||||
|
path: build/*.ipa
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload to TestFlight
|
||||||
|
if: >
|
||||||
|
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/ios-v')) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.upload_to_testflight == 'true')
|
||||||
|
run: |
|
||||||
|
xcrun altool \
|
||||||
|
--upload-app \
|
||||||
|
--type ios \
|
||||||
|
--file "$IPA_PATH" \
|
||||||
|
--apiKey "${{ secrets.APP_STORE_API_KEY_ID }}" \
|
||||||
|
--apiIssuer "${{ secrets.APP_STORE_API_ISSUER_ID }}"
|
||||||
|
env:
|
||||||
|
API_PRIVATE_KEY: ${{ secrets.APP_STORE_API_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
if [ -f "$IPA_PATH" ]; then
|
||||||
|
IPA_SIZE=$(du -h "$IPA_PATH" | cut -f1)
|
||||||
|
echo "### iOS Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Item | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| IPA Size | $IPA_SIZE |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Tag | ${GITHUB_REF#refs/tags/} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Build # | ${{ github.run_number }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "::error::IPA not found at $IPA_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Native Mobile App Build Guide
|
# Native Mobile App Build Guide
|
||||||
|
|
||||||
FarmaFinder ships as a PWA and is wrapped for the App Store / Play Store via [Capacitor 6](https://capacitorjs.com). The web frontend in `frontend/` is bundled into each native shell — there's no separate codebase for Android or iOS.
|
FarmaFinder ships as a PWA and is wrapped for the App Store / Play Store via [Capacitor 8](https://capacitorjs.com). The web frontend in `frontend/` is bundled into each native shell — there's no separate codebase for Android or iOS.
|
||||||
|
|
||||||
```
|
```
|
||||||
FarmaFinder/
|
FarmaFinder/
|
||||||
@@ -176,6 +176,103 @@ Both stores require: privacy policy URL, screenshots (multiple device sizes), an
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## CI/CD: GitHub Actions (iOS)
|
||||||
|
|
||||||
|
The project includes a GitHub Actions workflow for automated iOS builds, replacing the previous Bitrise pipeline.
|
||||||
|
|
||||||
|
### Workflow file
|
||||||
|
|
||||||
|
`.github/workflows/ios-build.yml` — builds the iOS app on every `ios-v*` tag push, or manually via `workflow_dispatch`.
|
||||||
|
|
||||||
|
### Trigger types
|
||||||
|
|
||||||
|
| Trigger | Behaviour |
|
||||||
|
|---------|-----------|
|
||||||
|
| `git push ios-v*` tag | Builds, archives, exports IPA, uploads to TestFlight |
|
||||||
|
| `workflow_dispatch` (manual) | Builds and optionally uploads to TestFlight (configurable) |
|
||||||
|
|
||||||
|
### Required GitHub Secrets
|
||||||
|
|
||||||
|
Set these in **Settings → Secrets and variables → Actions**:
|
||||||
|
|
||||||
|
| Secret | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `IOS_P12_BASE64` | Base64-encoded `.p12` distribution certificate |
|
||||||
|
| `IOS_P12_PASSWORD` | Password for the `.p12` file |
|
||||||
|
| `IOS_PROVISION_PROFILE_BASE64` | Base64-encoded `.mobileprovision` file |
|
||||||
|
| `IOS_PROVISION_PROFILE_UUID` | UUID of the provisioning profile (filename in `~/Library/MobileDevice/Provisioning Profiles/`) |
|
||||||
|
| `IOS_PROVISION_PROFILE_NAME` | Name of the provisioning profile (used in Xcode export) |
|
||||||
|
| `IOS_TEAM_ID` | Apple Developer Team ID (10-char alphanumeric) |
|
||||||
|
| `APP_STORE_API_KEY_ID` | App Store Connect API key ID |
|
||||||
|
| `APP_STORE_API_ISSUER_ID` | App Store Connect API issuer ID |
|
||||||
|
| `APP_STORE_API_PRIVATE_KEY` | App Store Connect API private key (`.p8` content) |
|
||||||
|
|
||||||
|
### Pipeline steps
|
||||||
|
|
||||||
|
1. **Test** (ubuntu-latest) — runs backend + frontend test suites
|
||||||
|
2. **Build iOS** (macos-14 — Apple Silicon runner):
|
||||||
|
- Installs Node 20, builds web bundle, syncs Capacitor
|
||||||
|
- Installs CocoaPods dependencies
|
||||||
|
- Imports signing certificate + provisioning profile from secrets
|
||||||
|
- Sets `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION` from tag
|
||||||
|
- Builds `.xcarchive`, exports `.ipa`
|
||||||
|
- Uploads IPA as GitHub artifact (30-day retention)
|
||||||
|
- Uploads to TestFlight via `xcrun altool`
|
||||||
|
|
||||||
|
### Local iOS build script
|
||||||
|
|
||||||
|
For local builds on macOS, use the provided script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/build-ios.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This performs the same steps (build web, sync, pod install, archive, export IPA) without the CI signing/TestFlight steps. Override the export-options path with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EXPORT_OPTIONS=path/to/export-options.plist ./scripts/build-ios.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export Options
|
||||||
|
|
||||||
|
`scripts/export-options.plist` configures the IPA export (App Store method, manual signing, team ID, provisioning profile). The workflow replaces placeholder values from secrets at build time.
|
||||||
|
|
||||||
|
### Release flow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Tag a release
|
||||||
|
git tag ios-v1.2.3
|
||||||
|
git push origin ios-v1.2.3
|
||||||
|
|
||||||
|
# 2. GitHub Actions runs automatically:
|
||||||
|
# test → build → archive → IPA artifact → TestFlight upload
|
||||||
|
|
||||||
|
# 3. Download the IPA from the Actions Artifacts section if needed
|
||||||
|
|
||||||
|
# 4. Submit via App Store Connect after TestFlight review
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migrating from Bitrise
|
||||||
|
|
||||||
|
The `bitrise.yml` is still present but no longer needed for iOS. To remove it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm bitrise.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Key differences from the Bitrise pipeline:
|
||||||
|
|
||||||
|
| Aspect | Bitrise | GitHub Actions |
|
||||||
|
|--------|---------|----------------|
|
||||||
|
| Runner | Bitrise macOS stack | `macos-14` (M1 Apple Silicon) |
|
||||||
|
| Signing | Bitrise certificate step | `apple-actions/import-codesign-certs` |
|
||||||
|
| TestFlight upload | `deploy-to-bitrise-io` | `xcrun altool` with App Store Connect API |
|
||||||
|
| Trigger | Tag `ios-v*` | Tag `ios-v*` or manual dispatch |
|
||||||
|
| Artifact | Bitrise dashboard | GitHub Actions Artifacts (30-day retention) |
|
||||||
|
| Cache | Bitrise stack caching | `actions/setup-node` cache |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Updating Capacitor / plugins
|
## Updating Capacitor / plugins
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Executable
+56
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCHEME="App"
|
||||||
|
PROJECT="ios/App/App.xcodeproj"
|
||||||
|
WORKSPACE="ios/App/App.xcworkspace"
|
||||||
|
ARCHIVE_PATH="build/App.xcarchive"
|
||||||
|
IPA_DIR="build"
|
||||||
|
EXPORT_OPTIONS="${EXPORT_OPTIONS:-scripts/export-options.plist}"
|
||||||
|
|
||||||
|
echo "=== FarmaFinder iOS Build Script ==="
|
||||||
|
|
||||||
|
echo "[1/6] Installing frontend dependencies..."
|
||||||
|
(cd frontend && npm ci)
|
||||||
|
|
||||||
|
echo "[2/6] Building web bundle..."
|
||||||
|
npm run build:web
|
||||||
|
|
||||||
|
echo "[3/6] Syncing Capacitor..."
|
||||||
|
if [ ! -d "ios/App/App.xcodeproj" ]; then
|
||||||
|
echo " Adding iOS platform..."
|
||||||
|
npx cap add ios
|
||||||
|
fi
|
||||||
|
npx cap sync ios
|
||||||
|
|
||||||
|
echo "[4/6] Installing CocoaPods..."
|
||||||
|
(cd ios/App && pod install --repo-update)
|
||||||
|
|
||||||
|
echo "[5/6] Archiving..."
|
||||||
|
xcodebuild archive \
|
||||||
|
-workspace "$WORKSPACE" \
|
||||||
|
-scheme "$SCHEME" \
|
||||||
|
-configuration Release \
|
||||||
|
-destination "generic/platform=iOS" \
|
||||||
|
-archivePath "$ARCHIVE_PATH" \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
| xcpretty --color || true
|
||||||
|
|
||||||
|
echo "[6/6] Exporting IPA..."
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath "$ARCHIVE_PATH" \
|
||||||
|
-exportOptionsPlist "$EXPORT_OPTIONS" \
|
||||||
|
-exportPath "$IPA_DIR" \
|
||||||
|
-allowProvisioningUpdates \
|
||||||
|
| xcpretty --color || true
|
||||||
|
|
||||||
|
IPA_FILE=$(find "$IPA_DIR" -name "*.ipa" -maxdepth 1 | head -1)
|
||||||
|
if [ -n "$IPA_FILE" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "=== Build complete ==="
|
||||||
|
echo "IPA: $IPA_FILE"
|
||||||
|
echo "Size: $(du -h "$IPA_FILE" | cut -f1)"
|
||||||
|
else
|
||||||
|
echo "ERROR: IPA not found in $IPA_DIR/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>method</key>
|
||||||
|
<string>app-store</string>
|
||||||
|
<key>teamID</key>
|
||||||
|
<string>YOUR_TEAM_ID</string>
|
||||||
|
<key>uploadSymbols</key>
|
||||||
|
<true/>
|
||||||
|
<key>uploadBitcode</key>
|
||||||
|
<false/>
|
||||||
|
<key>signingStyle</key>
|
||||||
|
<string>manual</string>
|
||||||
|
<key>provisioningProfiles</key>
|
||||||
|
<dict>
|
||||||
|
<key>net.hacecalor.farmafinder</key>
|
||||||
|
<string>YOUR_PROVISIONING_PROFILE_NAME</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Reference in New Issue
Block a user