Skip to content

Commit 1aba519

Browse files
committed
Refactor React Native CI/CD workflow: streamline job definitions, update artifact handling, and enhance release creation process
1 parent 5ab5a30 commit 1aba519

1 file changed

Lines changed: 131 additions & 169 deletions

File tree

Lines changed: 131 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Inspired by https://www.expobuilder.app
12
name: React Native CI/CD
23

34
on:
@@ -8,7 +9,6 @@ on:
89
# description: "Build type to run"
910
# options:
1011
# - dev
11-
# - prod-apk
1212
# - prod-aab
1313
# - ios-dev
1414
# - ios-prod
@@ -23,45 +23,36 @@ on:
2323
# - android
2424
# - ios
2525
# - all
26-
workflow_call:
27-
secrets:
28-
EXPO_TOKEN:
29-
required: true
30-
EXPO_APPLE_ID:
31-
required: true
32-
EXPO_APPLE_PASSWORD:
33-
required: true
34-
EXPO_TEAM_ID:
35-
required: true
36-
GOOGLE_PLAY_SERVICE_ACCOUNT:
37-
required: false
38-
SLACK_WEBHOOK:
39-
required: false
40-
DISCORD_WEBHOOK:
41-
required: false
42-
GITHUB_TOKEN:
43-
required: true
26+
workflow_call:
27+
secrets:
28+
EXPO_TOKEN:
29+
required: true
30+
EXPO_APPLE_ID:
31+
required: true
32+
EXPO_APPLE_PASSWORD:
33+
required: true
34+
EXPO_TEAM_ID:
35+
required: true
36+
GOOGLE_PLAY_SERVICE_ACCOUNT:
37+
required: false
38+
SLACK_WEBHOOK:
39+
required: false
40+
DISCORD_WEBHOOK:
41+
required: false
42+
GITHUB_TOKEN:
43+
required: true
4444

4545
env:
4646
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
4747
EXPO_APPLE_ID: ${{ secrets.EXPO_APPLE_ID }}
4848
EXPO_APPLE_PASSWORD: ${{ secrets.EXPO_APPLE_PASSWORD }}
4949
EXPO_TEAM_ID: ${{ secrets.EXPO_TEAM_ID }}
50-
# GOOGLE_PLAY_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
5150
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
52-
# DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
5351
NODE_OPTIONS: --openssl-legacy-provider
5452

5553
jobs:
56-
check-skip:
57-
runs-on: ubuntu-latest
58-
if: "!contains(github.event.head_commit.message, '[skip ci]')"
59-
steps:
60-
- name: Skip CI check
61-
run: echo "Proceeding with workflow"
62-
63-
test:
64-
needs: check-skip
54+
build-android:
55+
if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
6556
runs-on: ubuntu-latest
6657
steps:
6758
- name: 🏗 Checkout repository
@@ -86,30 +77,66 @@ jobs:
8677
${{ runner.os }}-yarn-
8778
8879
- name: 📦 Install dependencies
89-
run: yarn install
80+
run: |
81+
yarn install
82+
yarn global add eas-cli@latest
9083
91-
- name: 🧹 Run ESLint
92-
run: yarn lint
84+
- name: 📱 Setup EAS build cache
85+
uses: actions/cache@v3
86+
with:
87+
path: ~/.eas-build-local
88+
key: ${{ runner.os }}-eas-build-local-${{ hashFiles('**/package.json') }}
89+
restore-keys: |
90+
${{ runner.os }}-eas-build-local-
9391
94-
- name: 📢 Notify test results
95-
if: always()
96-
uses: rtCamp/action-slack-notify@v2
92+
- name: 🔄 Verify EAS CLI installation
93+
run: |
94+
echo "EAS CLI version:"
95+
eas --version
96+
97+
- name: 📋 Fix package.json main entry (Linux)
98+
run: |
99+
if ! command -v jq &> /dev/null; then
100+
sudo apt-get update && sudo apt-get install -y jq
101+
fi
102+
cp package.json package.json.bak
103+
jq '.main = "node_modules/expo/AppEntry.js"' package.json > package.json.tmp && mv package.json.tmp package.json
104+
cat package.json | grep "main"
105+
106+
- name: Build Development APK
107+
if: github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'dev' || github.event_name == 'push' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')
108+
run: |
109+
export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096"
110+
eas build --platform android --profile development --local --non-interactive --output=./app-dev.apk
97111
env:
98-
SLACK_WEBHOOK: ${{ env.SLACK_WEBHOOK }}
99-
SLACK_COLOR: ${{ job.status == 'success' && 'good' || 'danger' }}
100-
SLACK_TITLE: Test Results
101-
SLACK_MESSAGE: "Tests ${{ job.status == 'success' && 'passed ✅' || 'failed ❌' }}"
112+
NODE_ENV: development
113+
114+
- name: 📱 Build Production AAB
115+
if: github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'prod-aab' || github.event_name == 'push' && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')
116+
run: |
117+
export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096"
118+
eas build --platform android --profile production --local --non-interactive --output=./app-prod.aab
119+
env:
120+
NODE_ENV: production
102121

103-
build-and-release:
104-
needs: test
122+
- name: 🚀 Publish to Expo (optional)
123+
if: github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'publish-expo'
124+
run: |
125+
eas update --auto
126+
127+
- name: 📦 Upload Android artifacts
128+
uses: actions/upload-artifact@v4
129+
with:
130+
name: android-builds
131+
path: |
132+
./app-dev.apk
133+
./app-prod.aab
134+
if-no-files-found: ignore
135+
retention-days: 7
136+
137+
build-ios:
105138
if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
106-
strategy:
107-
matrix:
108-
platform: [android]
109-
include:
110-
- platform: ios
111-
runs-on: macos-latest
112-
runs-on: ${{ matrix.platform == 'ios' && 'macos-latest' || 'ubuntu-latest' }}
139+
runs-on: macos-latest
113140
steps:
114141
- name: 🏗 Checkout repository
115142
uses: actions/checkout@v4
@@ -150,124 +177,71 @@ jobs:
150177
echo "EAS CLI version:"
151178
eas --version
152179
153-
- name: 📋 Fix package.json main entry
180+
- name: 📋 Fix package.json main entry (macOS)
154181
run: |
155-
# Check if jq is installed, if not install it
156182
if ! command -v jq &> /dev/null; then
157-
echo "Installing jq..."
158-
sudo apt-get update && sudo apt-get install -y jq
183+
brew update || true
184+
brew install jq || true
159185
fi
186+
cp package.json package.json.bak
187+
jq '.main = "node_modules/expo/AppEntry.js"' package.json > package.json.tmp && mv package.json.tmp package.json
188+
cat package.json | grep "main"
160189
161-
# Fix the main entry in package.json
162-
if [ -f ./package.json ]; then
163-
# Create a backup
164-
cp package.json package.json.bak
165-
# Update the package.json
166-
jq '.main = "node_modules/expo/AppEntry.js"' package.json > package.json.tmp && mv package.json.tmp package.json
167-
echo "Updated package.json main entry"
168-
cat package.json | grep "main"
169-
else
170-
echo "package.json not found"
171-
exit 1
172-
fi
173-
174-
- name: 📋 Update metro.config.js for SVG support
175-
run: |
176-
if [ -f ./metro.config.js ]; then
177-
echo "Creating backup of metro.config.js"
178-
cp ./metro.config.js ./metro.config.js.backup
179-
echo "Updating metro.config.js to CommonJS format"
180-
cat > ./metro.config.js << 'EOFMARKER'
181-
/* eslint-disable @typescript-eslint/no-var-requires */
182-
const { getDefaultConfig } = require('expo/metro-config');
183-
184-
const config = getDefaultConfig(__dirname);
185-
186-
const { transformer, resolver } = config;
187-
188-
config.transformer = {
189-
...transformer,
190-
babelTransformerPath: require.resolve('react-native-svg-transformer/expo'),
191-
};
192-
193-
config.resolver = {
194-
...resolver,
195-
assetExts: resolver.assetExts.filter(ext => ext !== 'svg'),
196-
sourceExts: [...resolver.sourceExts, 'svg'],
197-
};
198-
199-
module.exports = config;
200-
EOFMARKER
201-
echo "metro.config.js updated to CommonJS format"
202-
else
203-
echo "metro.config.js not found"
204-
fi
205-
206-
- name: 📱 Build Development APK
207-
if: github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'dev' || github.event_name == 'push' && (matrix.platform == 'android' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')
208-
run: |
209-
# Build with increased memory limit
210-
export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096"
211-
eas build --platform android --profile development --local --non-interactive --output=./app-dev.apk
212-
env:
213-
NODE_ENV: development
214-
215-
- name: 📱 Build Production APK
216-
if: github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'prod-apk' || github.event_name == 'push' && (matrix.platform == 'android' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')
217-
run: |
218-
export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096"
219-
eas build --platform android --profile production-apk --local --non-interactive --output=./app-prod.apk
220-
env:
221-
NODE_ENV: production
222-
223-
- name: 📱 Build Production AAB
224-
if: github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'prod-aab' || github.event_name == 'push' && (matrix.platform == 'android' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')
225-
run: |
226-
export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096"
227-
eas build --platform android --profile production --local --non-interactive --output=./app-prod.aab
228-
env:
229-
NODE_ENV: production
230190
231191
- name: 📱 Build iOS Development
232-
if: (github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'ios-dev') && (matrix.platform == 'ios' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios')
192+
if: (github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'ios-dev') && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' || github.event_name == 'push')
233193
run: |
234194
export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096"
235195
eas build --platform ios --profile development --local --non-interactive --output=./app-ios-dev.app
236196
env:
237197
NODE_ENV: development
238198

239199
- name: 📱 Build iOS Production
240-
if: (github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'ios-prod') && (matrix.platform == 'ios' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios')
200+
if: (github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'ios-prod') && (github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios' || github.event_name == 'push')
241201
run: |
242202
export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096"
243203
eas build --platform ios --profile production --local --non-interactive --output=./app-ios-prod.ipa
244204
env:
245205
NODE_ENV: production
246206

247-
- name: 🚀 Publish to Expo
248-
if: github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'publish-expo'
207+
- name: 📦 Zip iOS .app (development)
249208
run: |
250-
eas update --auto
251-
env:
252-
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
253-
254-
# - name: 🚀 Submit to Play Store
255-
# if: (github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'publish-stores') && (matrix.platform == 'android' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'android')
256-
# run: |
257-
# eas submit -p android --path ./app-prod.aab --non-interactive
258-
# env:
259-
# EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
260-
# GOOGLE_PLAY_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
261-
262-
# - name: 🚀 Submit to App Store
263-
# if: (github.event.inputs.buildType == 'all' || github.event.inputs.buildType == 'publish-stores') && (matrix.platform == 'ios' || github.event.inputs.platform == 'all' || github.event.inputs.platform == 'ios')
264-
# run: |
265-
# eas submit -p ios --path ./app-ios-prod.ipa --non-interactive
266-
# env:
267-
# EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
268-
# EXPO_APPLE_ID: ${{ secrets.EXPO_APPLE_ID }}
269-
# EXPO_APPLE_PASSWORD: ${{ secrets.EXPO_APPLE_PASSWORD }}
270-
# EXPO_TEAM_ID: ${{ secrets.EXPO_TEAM_ID }}
209+
if [ -d "./app-ios-dev.app" ]; then
210+
ditto -c -k --sequesterRsrc --keepParent "./app-ios-dev.app" "./app-ios-dev.zip"
211+
fi
212+
213+
- name: 📦 Upload iOS artifacts
214+
uses: actions/upload-artifact@v4
215+
with:
216+
name: ios-builds
217+
path: |
218+
./app-ios-dev.app
219+
./app-ios-dev.zip
220+
./app-ios-prod.ipa
221+
if-no-files-found: ignore
222+
retention-days: 7
223+
224+
create-release:
225+
needs: [build-android, build-ios]
226+
runs-on: ubuntu-latest
227+
if: always()
228+
steps:
229+
- name: 🏗 Checkout repository
230+
uses: actions/checkout@v4
231+
232+
- name: ⬇️ Download Android artifacts
233+
if: ${{ needs.build-android.result == 'success' }}
234+
uses: actions/download-artifact@v4
235+
with:
236+
name: android-builds
237+
path: ./dist
238+
239+
- name: ⬇️ Download iOS artifacts
240+
if: ${{ needs.build-ios.result == 'success' }}
241+
uses: actions/download-artifact@v4
242+
with:
243+
name: ios-builds
244+
path: ./dist
271245

272246
- name: 🏷️ Generate build information
273247
id: build-info
@@ -276,46 +250,34 @@ jobs:
276250
BUILD_NUMBER=$(date +%Y%m%d%H%M)
277251
echo "version=$VERSION" >> $GITHUB_OUTPUT
278252
echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
279-
# Generate changelog from commit messages since last tag
280253
if git describe --tags --abbrev=0 > /dev/null 2>&1; then
281254
LAST_TAG=$(git describe --tags --abbrev=0)
282255
git log $LAST_TAG..HEAD --pretty=format:"- %s" > changelog.md
283256
else
284257
git log --pretty=format:"- %s" -n 10 > changelog.md
285258
fi
286259
287-
- name: 📝 Create GitHub Release
288-
uses: softprops/action-gh-release@v1
260+
- name: 📝 Create or Update GitHub Release
261+
uses: softprops/action-gh-release@v2
289262
with:
290263
draft: true
291264
name: "Release v${{ steps.build-info.outputs.version }}-${{ steps.build-info.outputs.build_number }}"
292265
tag_name: "v${{ steps.build-info.outputs.version }}-${{ steps.build-info.outputs.build_number }}"
293266
files: |
294-
./app-dev.apk
295-
./app-prod.apk
296-
./app-prod.aab
297-
./app-ios-dev.app
298-
./app-ios-prod.ipa
267+
./dist/app-dev.apk
268+
./dist/app-prod.aab
269+
./dist/app-ios-dev.zip
270+
./dist/app-ios-prod.ipa
271+
fail_on_unmatched_files: false
299272
body_path: changelog.md
300273
env:
301274
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
302-
- name: 📦 Upload build artifacts to GitHub
303-
uses: actions/upload-artifact@v4
304-
with:
305-
name: app-builds
306-
path: |
307-
./app-dev.apk
308-
./app-prod.apk
309-
./app-prod.aab
310-
./app-ios-dev.app
311-
./app-ios-prod.ipa
312-
retention-days: 7
313275

314-
- name: 📢 Notify build completion
276+
- name: 📢 Notify release status
315277
if: always()
316278
uses: rtCamp/action-slack-notify@v2
317279
env:
318280
SLACK_WEBHOOK: ${{ env.SLACK_WEBHOOK }}
319281
SLACK_COLOR: ${{ job.status == 'success' && 'good' || 'danger' }}
320-
SLACK_TITLE: Build Results
321-
SLACK_MESSAGE: "Build ${{ job.status == 'success' && 'completed successfully ✅' || 'failed ❌' }}"
282+
SLACK_TITLE: Release Results
283+
SLACK_MESSAGE: "Release ${{ job.status == 'success' && 'created/updated ✅' || 'failed ❌' }}"

0 commit comments

Comments
 (0)