├── .github ├── scripts │ └── decrypt_secret.sh └── workflows │ └── main.yml ├── .gitignore ├── App.tsx ├── README.md ├── app.json ├── assets ├── icon.png └── splash.png ├── babel.config.js ├── package.json ├── secrets └── .gitkeep ├── tsconfig.json └── yarn.lock /.github/scripts/decrypt_secret.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | gpg --quiet --batch --yes --decrypt --passphrase="$SECRET_FILES_PASSWORD" --output ./secrets/build.keystore ./secrets/build.keystore.gpg 3 | 4 | gpg --quiet --batch --yes --decrypt --passphrase="$SECRET_FILES_PASSWORD" --output ./secrets/cert.p12 ./secrets/cert.p12.gpg 5 | 6 | gpg --quiet --batch --yes --decrypt --passphrase="$SECRET_FILES_PASSWORD" --output ./secrets/profile.mobileprovision ./secrets/profile.mobileprovision.gpg 7 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Building mobile application 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | export: 9 | name: Export assets 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 12.x 17 | 18 | - uses: expo/expo-github-action@v5 19 | with: 20 | expo-version: 3.x 21 | expo-username: ${{ secrets.EXPO_CLI_USERNAME }} 22 | expo-password: ${{ secrets.EXPO_CLI_PASSWORD }} 23 | 24 | - run: yarn install 25 | 26 | - run: expo export --public-url ${{ secrets.ASSETS_URL }} 27 | 28 | - uses: sebastianpopp/ftp-action@v2.0.0 29 | with: 30 | host: ${{ secrets.ASSETS_FTP_HOST }} 31 | user: ${{ secrets.ASSETS_FTP_USER }} 32 | password: ${{ secrets.ASSETS_FTP_PASSWORD }} 33 | localDir: dist 34 | remoteDir: ${{ secrets.ASSETS_FTP_REMOTE_DIR }} 35 | 36 | apk: 37 | name: Build .apk file 38 | runs-on: ubuntu-latest 39 | needs: export 40 | steps: 41 | - uses: actions/checkout@v2 42 | 43 | - uses: actions/setup-node@v1 44 | with: 45 | node-version: 12.x 46 | 47 | - run: ./.github/scripts/decrypt_secret.sh 48 | env: 49 | SECRET_FILES_PASSWORD: ${{ secrets.SECRET_FILES_PASSWORD }} 50 | 51 | - uses: actions/cache@v2 52 | id: turtle-cache 53 | with: 54 | path: ~/.turtle 55 | key: ${{ runner.os }}-turtle 56 | 57 | - run: | 58 | yarn install 59 | npm install -g turtle-cli 60 | turtle build:android -o ./build.apk --keystore-path ./secrets/build.keystore --keystore-alias ${{ secrets.ANDROID_KEYSTORE_ALIAS }} --public-url ${{ secrets.ASSETS_URL }}/android-index.json -t apk 61 | env: 62 | EXPO_ANDROID_KEY_PASSWORD: ${{ secrets.EXPO_ANDROID_KEY_PASSWORD }} 63 | EXPO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.EXPO_ANDROID_KEYSTORE_PASSWORD }} 64 | 65 | - name: Archive .apk build 66 | uses: actions/upload-artifact@v1 67 | with: 68 | name: build.apk 69 | path: ./build.apk 70 | 71 | ipa: 72 | name: Build .ipa file 73 | runs-on: macos-latest 74 | needs: export 75 | steps: 76 | - uses: actions/checkout@v2 77 | 78 | - uses: actions/setup-node@v1 79 | with: 80 | node-version: 12.x 81 | 82 | - run: ./.github/scripts/decrypt_secret.sh 83 | env: 84 | SECRET_FILES_PASSWORD: ${{ secrets.SECRET_FILES_PASSWORD }} 85 | 86 | - uses: actions/cache@v2 87 | id: turtle-cache 88 | with: 89 | path: ~/.turtle 90 | key: ${{ runner.os }}-turtle 91 | 92 | - run: | 93 | yarn install 94 | npm install -g turtle-cli 95 | turtle build:ios -o build.ipa --team-id ${{ secrets.APPLE_TEAM_ID }} --dist-p12-path ./secrets/cert.p12 --provisioning-profile-path ./secrets/profile.mobileprovision --public-url ${{ secrets.ASSETS_URL }}/ios-index.json 96 | env: 97 | EXPO_IOS_DIST_P12_PASSWORD: ${{ secrets.P12_PASSWORD }} 98 | 99 | - name: Archive .ipa build 100 | uses: actions/upload-artifact@v1 101 | with: 102 | name: build.ipa 103 | path: ./build.ipa 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .expo 2 | .expo-shared 3 | dist 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.keystore 10 | *.apk 11 | *.mobileprovision 12 | *.orig.* 13 | web-build/ 14 | web-report/ 15 | 16 | # secrets 17 | # secrets/ 18 | !secrets/*.gpg 19 | 20 | # macOS 21 | .DS_Store 22 | node_modules 23 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | 4 | export default function App() { 5 | return ( 6 | 7 | Open up App.tsx to start working on your app! 8 | 9 | ); 10 | } 11 | 12 | const styles = StyleSheet.create({ 13 | container: { 14 | flex: 1, 15 | backgroundColor: '#fff', 16 | alignItems: 'center', 17 | justifyContent: 'center', 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # expo-standalone-with-github-actions 2 | Sample expo project, built with turtle-cli and Github Actions 3 | 4 | ## Required secrets: 5 | To configure these secrets, you have to go to `Settings` -> `Secrets` -> `New secret` in your repository menu. 6 | 7 | ### Android: 8 | - ANDROID_KEYSTORE_ALIAS - Keystore alias 9 | - EXPO_ANDROID_KEYSTORE_PASSWORD - Keystore password 10 | - EXPO_ANDROID_KEY_PASSWORD - Key password 11 | 12 | ### iOS: 13 | - P12_PASSWORD - Password for .p12 file 14 | - APPLE_TEAM_ID - Your Apple Team ID 15 | 16 | ### Common: 17 | - ASSETS_FTP_HOST - Host for assets FTP 18 | - ASSETS_FTP_PASSWORD - Credentials for assets FTP 19 | - ASSETS_FTP_USER - Credentials for assets FTP 20 | - ASSETS_FTP_REMOTE_DIR - Remote directory on assets FTP 21 | - ASSETS_URL - Public URL to assets (required SSL) 22 | - EXPO_CLI_PASSWORD - Expo.io credentials 23 | - EXPO_CLI_USERNAME - Expo.io credentials 24 | - SECRET_FILES_PASSWORD - Password to decrypt GPG files 25 | 26 | ## Required files: 27 | To build an application, you have to send to your repository encrypted (by GPG) files. To encrypt each of them, you can use this command: 28 | 29 | `$ gpg --symmetric --cipher-algo AES256 {target_file}` 30 | 31 | Remember to use `SECRET_FILES_PASSWORD` to secure these files. 32 | 33 | ### Android: 34 | - `./secrets/build.keystore.gpg` - encrypted your .keystore file - you can generate it by command: 35 | 36 | `$ keytool -genkeypair -v -keystore {filename}.keystore -alias {keystore-alias} -keyalg RSA -keysize 2048 -validity 10000` 37 | 38 | ### iOS: 39 | - `./secrets/cert.p12.gpg` - encrypted .p12 certification file - generated in AppStore Connect panel 40 | - `./secrets/profile.mobileprovision.gpg` - encrypted Provisioning Profile file - generated in AppStore Connect panel 41 | 42 | ## TODO: 43 | - [ ] PUSH notifications config (google-services.json file) 44 | - [ ] Google Analytics config (GoogleService-Info.plist file) 45 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "expoGithubActionsBuild", 4 | "slug": "expoGithubActionsBuild", 5 | "platforms": [ 6 | "ios", 7 | "android", 8 | "web" 9 | ], 10 | "version": "1.0.0", 11 | "orientation": "portrait", 12 | "icon": "./assets/icon.png", 13 | "splash": { 14 | "image": "./assets/splash.png", 15 | "resizeMode": "contain", 16 | "backgroundColor": "#ffffff" 17 | }, 18 | "updates": { 19 | "fallbackToCacheTimeout": 0 20 | }, 21 | "assetBundlePatterns": [ 22 | "**/*" 23 | ], 24 | "android": { 25 | "package": "pl.rduraj.expoSample", 26 | "versionCode": 1 27 | }, 28 | "ios": { 29 | "bundleIdentifier": "pl.rduraj.expo-sample", 30 | "supportsTablet": true 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rduraj/expo-standalone-with-github-actions/8426b16c0683bb118075f44bace42a2c8cc9725b/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rduraj/expo-standalone-with-github-actions/8426b16c0683bb118075f44bace42a2c8cc9725b/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "expo": "~37.0.3", 12 | "react": "~16.9.0", 13 | "react-dom": "~16.9.0", 14 | "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz", 15 | "react-native-screens": "~2.2.0", 16 | "react-native-web": "~0.11.7" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.8.6", 20 | "@types/react": "~16.9.23", 21 | "@types/react-native": "~0.61.17", 22 | "babel-preset-expo": "~8.1.0", 23 | "typescript": "~3.8.3" 24 | }, 25 | "private": true 26 | } 27 | -------------------------------------------------------------------------------- /secrets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rduraj/expo-standalone-with-github-actions/8426b16c0683bb118075f44bace42a2c8cc9725b/secrets/.gitkeep -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true, 10 | "strict": true 11 | } 12 | } 13 | --------------------------------------------------------------------------------