Skip to content

Commit 6343c3a

Browse files
committed
Add iOS release pipeline and project
1 parent 6e5947b commit 6343c3a

22 files changed

Lines changed: 1032 additions & 0 deletions

File tree

.github/workflows/ios-release.yml

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
name: iOS Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch:
8+
inputs:
9+
release_tag:
10+
description: "Attach the built IPA to this GitHub Release tag (optional)"
11+
required: false
12+
type: string
13+
publish_target:
14+
description: "Where to publish after the IPA is built"
15+
required: true
16+
type: choice
17+
default: all
18+
options:
19+
- none
20+
- all
21+
- testflight
22+
- app-store
23+
24+
permissions:
25+
contents: write
26+
27+
concurrency:
28+
group: ios-release-${{ github.ref }}
29+
cancel-in-progress: true
30+
31+
env:
32+
NODE_VERSION: "20"
33+
RUBY_VERSION: "3.3"
34+
IOS_BUNDLE_ID: "com.monkeycode.mobile"
35+
36+
jobs:
37+
ios:
38+
runs-on: macos-latest
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Ensure iOS project exists
43+
run: test -d mobile/ios/App
44+
45+
- uses: pnpm/action-setup@v4
46+
with:
47+
version: 9
48+
49+
- uses: actions/setup-node@v4
50+
with:
51+
node-version: ${{ env.NODE_VERSION }}
52+
cache: pnpm
53+
cache-dependency-path: |
54+
frontend/pnpm-lock.yaml
55+
mobile/pnpm-lock.yaml
56+
57+
- uses: ruby/setup-ruby@v1
58+
with:
59+
ruby-version: ${{ env.RUBY_VERSION }}
60+
61+
- name: Install fastlane gems
62+
working-directory: mobile/ios
63+
run: bundle install --jobs 4 --retry 3
64+
65+
- name: Set version
66+
shell: bash
67+
run: |
68+
set -euo pipefail
69+
70+
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
71+
V="${GITHUB_REF_NAME#v}"
72+
else
73+
V=$(node -p "require('./frontend/package.json').version")
74+
fi
75+
76+
if [[ "$V" =~ ^[0-9]+\.[0-9]+$ ]]; then
77+
V="${V}.0"
78+
fi
79+
80+
BUILD_NUMBER="${{ github.run_number }}"
81+
IPA_NAME="MonkeyCode-${V}-ios-release.ipa"
82+
83+
echo "APP_VERSION=$V" >> "$GITHUB_ENV"
84+
echo "BUILD_NUMBER=$BUILD_NUMBER" >> "$GITHUB_ENV"
85+
echo "IPA_NAME=$IPA_NAME" >> "$GITHUB_ENV"
86+
87+
node -e "
88+
const fs=require('fs');
89+
const p='mobile/package.json';
90+
const j=JSON.parse(fs.readFileSync(p,'utf8'));
91+
j.version=process.argv[1];
92+
fs.writeFileSync(p,JSON.stringify(j,null,2)+'\n');
93+
" "$V"
94+
95+
perl -0pi -e "s/MARKETING_VERSION = [^;]+;/MARKETING_VERSION = ${V};/g; s/CURRENT_PROJECT_VERSION = [^;]+;/CURRENT_PROJECT_VERSION = ${BUILD_NUMBER};/g" mobile/ios/App/App.xcodeproj/project.pbxproj
96+
97+
- name: Install frontend dependencies
98+
working-directory: frontend
99+
run: pnpm install --frozen-lockfile
100+
101+
- name: Install mobile dependencies
102+
working-directory: mobile
103+
run: pnpm install --frozen-lockfile
104+
105+
- name: Build frontend
106+
working-directory: frontend
107+
run: pnpm run build
108+
109+
- name: Capacitor sync iOS
110+
working-directory: mobile
111+
run: pnpm exec cap sync ios --no-build
112+
113+
- name: Install signing assets
114+
shell: bash
115+
env:
116+
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
117+
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
118+
IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
119+
run: |
120+
set -euo pipefail
121+
122+
: "${IOS_CERTIFICATE_BASE64:?Missing IOS_CERTIFICATE_BASE64}"
123+
: "${IOS_CERTIFICATE_PASSWORD:?Missing IOS_CERTIFICATE_PASSWORD}"
124+
: "${IOS_PROVISIONING_PROFILE_BASE64:?Missing IOS_PROVISIONING_PROFILE_BASE64}"
125+
126+
CERT_PATH="$RUNNER_TEMP/build_certificate.p12"
127+
PROFILE_PATH="$RUNNER_TEMP/build_profile.mobileprovision"
128+
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
129+
KEYCHAIN_PASSWORD="$(openssl rand -base64 24)"
130+
131+
echo "$IOS_CERTIFICATE_BASE64" | base64 -D > "$CERT_PATH"
132+
echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 -D > "$PROFILE_PATH"
133+
134+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
135+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
136+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
137+
security import "$CERT_PATH" -P "$IOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
138+
security list-keychains -d user -s "$KEYCHAIN_PATH"
139+
security default-keychain -s "$KEYCHAIN_PATH"
140+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
141+
142+
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
143+
security cms -D -i "$PROFILE_PATH" > "$RUNNER_TEMP/profile.plist"
144+
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" "$RUNNER_TEMP/profile.plist")
145+
PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print Name" "$RUNNER_TEMP/profile.plist")
146+
cp "$PROFILE_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles/$PROFILE_UUID.mobileprovision"
147+
148+
echo "IOS_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
149+
echo "IOS_KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> "$GITHUB_ENV"
150+
echo "IOS_PROFILE_NAME=$PROFILE_NAME" >> "$GITHUB_ENV"
151+
152+
- name: Build signed IPA
153+
working-directory: mobile/ios
154+
env:
155+
IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}
156+
IOS_BUNDLE_ID: ${{ env.IOS_BUNDLE_ID }}
157+
IOS_PROFILE_NAME: ${{ env.IOS_PROFILE_NAME }}
158+
IOS_OUTPUT_NAME: ${{ env.IPA_NAME }}
159+
run: bundle exec fastlane ios build_release
160+
161+
- name: Resolve IPA path
162+
shell: bash
163+
run: |
164+
set -euo pipefail
165+
IPA_PATH="mobile/ios/App/output/${IPA_NAME}"
166+
test -f "$IPA_PATH"
167+
echo "IPA_PATH=$IPA_PATH" >> "$GITHUB_ENV"
168+
169+
- uses: actions/upload-artifact@v4
170+
with:
171+
name: ios-ipa
172+
path: ${{ env.IPA_PATH }}
173+
if-no-files-found: error
174+
175+
- name: Resolve release tag
176+
shell: bash
177+
run: |
178+
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
179+
echo "RELEASE_TAG=${GITHUB_REF_NAME}" >> "$GITHUB_ENV"
180+
elif [[ -n "${{ inputs.release_tag }}" ]]; then
181+
echo "RELEASE_TAG=${{ inputs.release_tag }}" >> "$GITHUB_ENV"
182+
fi
183+
184+
- name: Upload to GitHub Release
185+
if: env.RELEASE_TAG != ''
186+
uses: softprops/action-gh-release@v2
187+
with:
188+
tag_name: ${{ env.RELEASE_TAG }}
189+
files: ${{ env.IPA_PATH }}
190+
191+
- name: Upload to TestFlight
192+
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && (inputs.publish_target == 'testflight' || inputs.publish_target == 'all'))
193+
working-directory: mobile/ios
194+
env:
195+
IOS_IPA_PATH: ${{ env.IPA_PATH }}
196+
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
197+
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
198+
APP_STORE_CONNECT_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }}
199+
run: bundle exec fastlane ios upload_testflight ipa:"$IOS_IPA_PATH"
200+
201+
- name: Submit to App Store
202+
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && (inputs.publish_target == 'app-store' || inputs.publish_target == 'all'))
203+
working-directory: mobile/ios
204+
env:
205+
IOS_IPA_PATH: ${{ env.IPA_PATH }}
206+
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
207+
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
208+
APP_STORE_CONNECT_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }}
209+
run: bundle exec fastlane ios submit_app_store ipa:"$IOS_IPA_PATH"
210+
211+
- name: Cleanup signing keychain
212+
if: always() && env.IOS_KEYCHAIN_PATH != ''
213+
shell: bash
214+
run: |
215+
security delete-keychain "$IOS_KEYCHAIN_PATH" || true

mobile/ios/.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
App/build
2+
App/Pods
3+
App/output
4+
App/App/public
5+
DerivedData
6+
xcuserdata
7+
8+
# Cordova plugins for Capacitor
9+
capacitor-cordova-ios-plugins
10+
11+
# Generated Config files
12+
App/App/capacitor.config.json
13+
App/App/config.xml

0 commit comments

Comments
 (0)