├── assets ├── icon.png ├── favicon.png └── splash.png ├── babel.config.js ├── README.md ├── .expo-shared └── assets.json ├── .gitignore ├── tsconfig.json ├── jest.config.js ├── test └── App.test.tsx ├── app.config.js ├── App.tsx ├── .eslintrc.js ├── app.json ├── .github └── workflows │ ├── multi_environment_test.yml │ ├── deploy_staging.yml │ ├── testing.yml │ └── deploy_prod.yml └── package.json /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justussoh/github-actions-expo-boiler-template/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justussoh/github-actions-expo-boiler-template/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justussoh/github-actions-expo-boiler-template/HEAD/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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expo working together with GitHub Actions starter 2 | 3 | This is the example explained in a dev.to post, more details will be available soon. -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | 12 | # macOS 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | preset: "react-native", 4 | transformIgnorePatterns: [ 5 | "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" 6 | ] 7 | }; -------------------------------------------------------------------------------- /test/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from "react-native"; 3 | import { render } from "@testing-library/react-native"; 4 | 5 | describe('example test', () => { 6 | it('get by text', () => { 7 | const { getByText } = render(Hello World); 8 | expect(getByText("Hello World")).not.toBeNull(); 9 | }); 10 | }); -------------------------------------------------------------------------------- /app.config.js: -------------------------------------------------------------------------------- 1 | export default ({ config }) => { 2 | return { 3 | ...config, 4 | version: process.env.APP_BINARY_VERSION || "1.0.0", 5 | android: { 6 | ...config.android, 7 | versionCode: parseInt(process.env.APP_BUILD_VERSION || 1) 8 | }, 9 | ios: { 10 | ...config.ios, 11 | buildNumber: process.env.APP_BUILD_VERSION || "1" 12 | } 13 | }; 14 | }; -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar'; 2 | import React from 'react'; 3 | import { StyleSheet, Text, View } from 'react-native'; 4 | 5 | export default function App() { 6 | return ( 7 | 8 | Hello World 9 | 10 | 11 | ); 12 | } 13 | 14 | const styles = StyleSheet.create({ 15 | container: { 16 | flex: 1, 17 | backgroundColor: '#fff', 18 | alignItems: 'center', 19 | justifyContent: 'center', 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2020": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "universe/native", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module" 16 | }, 17 | "plugins": [ 18 | "@typescript-eslint" 19 | ], 20 | "rules": { 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "github-actions-expo-boiler-template", 4 | "slug": "github-actions-expo-boiler-template", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": ["**/*"], 17 | "android": { 18 | "package": "me.jszh.expo.example" 19 | }, 20 | "ios": { 21 | "supportsTablet": true 22 | }, 23 | "web": { 24 | "favicon": "./assets/favicon.png" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/multi_environment_test.yml: -------------------------------------------------------------------------------- 1 | name: Test Different Development Environments 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Lint & Test 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macOS-latest, windows-latest] 12 | node: [10, 12, 13] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - name: Cache Node Modules 19 | uses: actions/cache@v2 20 | env: 21 | cache-name: cache-node-modules 22 | with: 23 | # npm cache files are stored in `~/.npm` on Linux/macOS 24 | path: ~/.npm 25 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 26 | restore-keys: | 27 | ${{ runner.os }}-build-${{ env.cache-name }}- 28 | ${{ runner.os }}-build- 29 | ${{ runner.os }}- 30 | - name: Install Packages 31 | run: npm install 32 | - name: Typecheck 33 | run: npx --no-install tsc --noEmit 34 | - name: Check Lint 35 | run: npm run lint 36 | - name: Test 37 | run: npm run test -------------------------------------------------------------------------------- /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 | "lint": "eslint --ext .js,.ts,.tsx,.mdx --max-warnings 0", 10 | "lint:fix": "npm run lint -- --fix", 11 | "test": "jest" 12 | }, 13 | "dependencies": { 14 | "expo": "~38.0.8", 15 | "expo-status-bar": "^1.0.2", 16 | "react": "~16.11.0", 17 | "react-dom": "~16.11.0", 18 | "react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz", 19 | "react-native-web": "~0.11.7" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.8.6", 23 | "@testing-library/react-native": "^7.0.2", 24 | "@types/jest": "^26.0.10", 25 | "@types/node": "^14.6.0", 26 | "@types/react": "~16.9.41", 27 | "@types/react-native": "~0.62.13", 28 | "@types/react-test-renderer": "^16.9.3", 29 | "@typescript-eslint/eslint-plugin": "^3.9.1", 30 | "@typescript-eslint/parser": "^3.9.1", 31 | "eslint": "^7.7.0", 32 | "eslint-config-universe": "^4.0.0", 33 | "jest": "^26.4.0", 34 | "react-test-renderer": "^16.13.1", 35 | "typescript": "~3.9.5" 36 | }, 37 | "private": true 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/deploy_staging.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Staging 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | test: 10 | name: Lint & Test 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 12.x 17 | - name: Cache Node Modules 18 | uses: actions/cache@v2 19 | env: 20 | cache-name: cache-node-modules 21 | with: 22 | path: ~/.npm 23 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 24 | restore-keys: | 25 | ${{ runner.os }}-build-${{ env.cache-name }}- 26 | ${{ runner.os }}-build- 27 | ${{ runner.os }}- 28 | - name: Install Packages 29 | run: npm install 30 | - name: Typecheck 31 | run: npx --no-install tsc --noEmit 32 | - name: Check Lint 33 | run: npm run lint 34 | - name: Test 35 | run: npm run test 36 | deploy_staging: 37 | name: Deploy to Staging 38 | needs: test 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions/setup-node@v1 43 | with: 44 | node-version: 12.x 45 | - uses: expo/expo-github-action@v5 46 | with: 47 | expo-packager: npm 48 | expo-username: ${{ secrets.EXPO_CLI_USERNAME }} 49 | expo-password: ${{ secrets.EXPO_CLI_PASSWORD }} 50 | expo-cache: true 51 | - name: Install Packages 52 | run: npm install 53 | - name: Expo Publish Channel 54 | run: expo publish --non-interactive --release-channel staging 55 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Branch Preview 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Lint & Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: 12.x 14 | - name: Cache Node Modules 15 | uses: actions/cache@v2 16 | env: 17 | cache-name: cache-node-modules 18 | with: 19 | # npm cache files are stored in `~/.npm` on Linux/macOS 20 | path: ~/.npm 21 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 22 | restore-keys: | 23 | ${{ runner.os }}-build-${{ env.cache-name }}- 24 | ${{ runner.os }}-build- 25 | ${{ runner.os }}- 26 | - name: Install Packages 27 | run: npm install 28 | # Perform a type check if using typescript before testing 29 | - name: Typecheck 30 | run: npx --no-install tsc --noEmit 31 | - name: Check Lint 32 | run: npm run lint 33 | - name: Test 34 | run: npm run test 35 | deploy_branch_preview: 36 | name: Deploy Branch Preview 37 | needs: test 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | - uses: actions/setup-node@v1 42 | with: 43 | node-version: 12.x 44 | - uses: expo/expo-github-action@v5 45 | with: 46 | expo-packager: npm 47 | expo-username: ${{ secrets.EXPO_CLI_USERNAME }} 48 | expo-password: ${{ secrets.EXPO_CLI_PASSWORD }} 49 | expo-cache: true 50 | - name: Cache Node Modules 51 | uses: actions/cache@v2 52 | env: 53 | cache-name: cache-node-modules 54 | with: 55 | path: ~/.npm 56 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 57 | restore-keys: | 58 | ${{ runner.os }}-build-${{ env.cache-name }}- 59 | ${{ runner.os }}-build- 60 | ${{ runner.os }}- 61 | - name: Install Packages 62 | run: npm install 63 | - name: Expo Publish Channel 64 | run: expo publish --non-interactive --release-channel pr${{ github.event.number }} 65 | - name: Add Comment To PR 66 | uses: mshick/add-pr-comment@v1 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | EXPO_PROJECT: "@justussoh/github-actions-expo-boiler-template" # Put in your own Expo project name here 70 | with: 71 | message: | 72 | ## Application 73 | ![Expo QR](https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=exp://exp.host/${{ env.EXPO_PROJECT }}?release-channel=pr${{ github.event.number }}) 74 | Published to https://exp.host/${{ env.EXPO_PROJECT }}?release-channel=pr${{ github.event.number }} -------------------------------------------------------------------------------- /.github/workflows/deploy_prod.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | on: 3 | push: 4 | tags: 5 | - "prod-[1-9]+.[0-9]+.[0-9]+" # Push events to matching prod-*, i.e.prod-20.15.10 6 | 7 | jobs: 8 | test: 9 | name: Lint & Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12.x 16 | - name: Install Packages 17 | run: npm install 18 | - name: Typecheck 19 | run: npx --no-install tsc --noEmit 20 | - name: Check Lint 21 | run: npm run lint 22 | - name: Test 23 | run: npm run test 24 | deploy_prod: 25 | name: Deploy To Production 26 | needs: test 27 | runs-on: ubuntu-latest 28 | outputs: 29 | releaseChannel: ${{ steps.releaseChannel.outputs.releaseChannel }} 30 | latestBinaryVersion: ${{ steps.latestBinaryVersion.outputs.version }} 31 | steps: 32 | - uses: actions/checkout@v2 33 | with: 34 | ref: ${{ github.head_ref }} 35 | - name: Fetch Tags 36 | run: | 37 | git fetch --prune --unshallow --tags -f 38 | - uses: actions/setup-node@v1 39 | with: 40 | node-version: 12.x 41 | - uses: expo/expo-github-action@v5 42 | with: 43 | expo-packager: npm 44 | expo-username: ${{ secrets.EXPO_CLI_USERNAME }} 45 | expo-password: ${{ secrets.EXPO_CLI_PASSWORD }} 46 | expo-cache: true 47 | - uses: rlespinasse/github-slug-action@v2.x 48 | - name: Generate Release Channel # Release Channels are named prod-, i.e. prod-1, prod-3 49 | id: releaseChannel 50 | run: | 51 | RELEASE_CHANNEL=$(echo ${{ env.GITHUB_REF_SLUG }} | sed -r 's/\.[0-9]+\.[0-9]+$//') 52 | echo "::set-output name=releaseChannel::$RELEASE_CHANNEL" 53 | - name: Install Packages 54 | run: npm install 55 | - name: Get Latest Binary Version # Binary Version will be x.x.x based on the latest tag 56 | id: latestBinaryVersion 57 | run: | 58 | # Release tag finds the lastest tag in the tree branch - i.e. prod-x.x.x 59 | RELEASE_TAG=$(echo $(git describe --tags --abbrev=0)) 60 | # Using param substitution, we output x.x.x instead 61 | echo "::set-output name=version::${RELEASE_TAG#*-}" 62 | - name: Echo Version Details 63 | run: | 64 | echo Build number is $GITHUB_RUN_NUMBER 65 | echo Latest release is ${{ steps.latestBinaryVersion.outputs.version }} 66 | - name: Expo Publish Channel 67 | run: expo publish --non-interactive --release-channel=${{ steps.releaseChannel.outputs.releaseChannel }} 68 | create_release: 69 | name: Create Release 70 | needs: deploy_prod 71 | runs-on: ubuntu-latest 72 | steps: 73 | - uses: actions/checkout@v2 74 | - uses: rlespinasse/github-slug-action@v2.x 75 | - name: Generate Changelog 76 | id: changelog 77 | uses: metcalfc/changelog-generator@v0.4.0 78 | with: 79 | myToken: ${{ secrets.GITHUB_TOKEN }} 80 | base-ref: 'prod-0' 81 | - name: Creating Release 82 | uses: ncipollo/release-action@v1 83 | with: 84 | body: | 85 | Changes in this Release: 86 | ${{ steps.changelog.outputs.changelog }} 87 | token: ${{ secrets.GITHUB_TOKEN }} 88 | name: Release ${{ env.GITHUB_REF_SLUG }} 89 | allowUpdates: true 90 | build_android: 91 | needs: [deploy_prod, create_release] 92 | runs-on: ubuntu-latest 93 | steps: 94 | - uses: actions/checkout@v2 95 | - uses: rlespinasse/github-slug-action@v2.x 96 | - uses: expo/expo-github-action@v5 97 | with: 98 | expo-packager: npm 99 | expo-username: ${{ secrets.EXPO_CLI_USERNAME }} 100 | expo-password: ${{ secrets.EXPO_CLI_PASSWORD }} 101 | expo-cache: true 102 | - name: Install Packages 103 | run: npm install 104 | - name: Build Android Release 105 | env: 106 | APP_BUILD_VERSION: ${{ github.run_number }} 107 | APP_BINARY_VERSION: ${{ needs.deploy_prod.outputs.latestBinaryVersion }} 108 | run: | 109 | expo build:android --release-channel=${{ needs.deploy_prod.outputs.releaseChannel }} > buildLogAndroid.txt 110 | cat buildLogAndroid.txt 111 | - name: Parse Asset URL 112 | id: androidUrl 113 | run: | 114 | ASSET_URL=$(cat buildLogAndroid.txt | tail | egrep -o 'https?://expo\.io/artifacts/[^ ]+') 115 | echo The android url is $ASSET_URL 116 | echo "::set-output name=assetUrl::$ASSET_URL" 117 | - name: Download APK Asset 118 | run: wget -O example-${{ env.GITHUB_REF_SLUG }}.apk ${{ steps.androidUrl.outputs.assetUrl }} 119 | - name: Upload Release Asset 120 | uses: svenstaro/upload-release-action@v2 121 | with: 122 | repo_token: ${{ secrets.GITHUB_TOKEN }} 123 | file: ./example-${{ env.GITHUB_REF_SLUG }}.apk 124 | asset_name: example-${{ env.GITHUB_REF_SLUG }}.apk 125 | tag: ${{ github.ref }} --------------------------------------------------------------------------------