Executando verificação de segurança...
5

Como criar um pipeline CI/CD para apps React Native com GitHub Actions — um guia prático do zero

Semana passada, a equipe do React Native lançou a versão 0.74.4 com pequenos ajustes de segurança para Android 14 e correções de regressões no Metro. A novidade chamou pouco a atenção, mas reforça uma tendência cada vez mais forte: a necessidade de automatizar processos que, antigamente, demandavam horas de trabalho manual. Se você ainda envia builds para a Play Store ou TestFlight copiando .aab, .ipa e subindo por interfaces web, este texto é para você.

Vamos construir juntos um pipeline de integração e entrega contínua (CI/CD) para um app React Native usando exclusivamente o GitHub Actions, sem precisar de Jenkins, Bitrise ou CircleCI. A beleza dessa abordagem é que ela serve tanto para quem está começando (basta ter um repositório público no GitHub) quanto para quem já tem um projeto maduro e quer economizar dinheiro em ferramentas pagas.

O que você vai levar deste artigo

Ao final, você terá:

  • Um workflow que roda testes, lint e TypeScript em cada Pull Request.
  • Builds de Preview que chegam automaticamente no celular via Expo EAS Update ou Firebase App Distribution.
  • Uma release de produção gerada toda vez que um PR é mergeado na branch main.
  • Tudo isso custando zero reais (ou poucos dólares se precisar de macOS para compilar iOS).

Preparação: estrutura mínima do projeto

Antes de encanar YAML, certifique-se de que seu projeto atenda a estes pré-requisitos:

  1. React Native 0.73+ (ou Expo SDK 50+ se usar Expo).
  2. package.json contendo:
    • yarn test (ou npm run test) executando Jest.
    • yarn lint executando ESLint.
    • yarn type-check executando tsc --noEmit.
  3. Um arquivo .nvmrc fixando a versão do Node (ex: 18.18.0).
  4. Um repositório público no GitHub (privado também funcila, mas consome minutos de Actions).

Exemplo de scripts úteis no package.json:

{
  "scripts": {
    "test": "jest --passWithNoTests",
    "lint": "eslint src --ext .js,.jsx,.ts,.tsx",
    "type-check": "tsc --noEmit",
    "build:android": "cd android && ./gradlew assembleRelease",
    "build:ios": "cd ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release"
  }
}

Passo 1 — Workflow de validação em PR

Crie a pasta .github/workflows e dentro dela o arquivo pr-check.yml:

name: PR Check

on:
  pull_request:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "yarn"
      - name: Install deps
        run: yarn install --frozen-lockfile
      - name: Lint
        run: yarn lint
      - name: Type-check
        run: yarn type-check
      - name: Test
        run: yarn test --ci --coverage
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

Como funciona

  • O workflow só dispara quando alguém abre ou atualiza um PR para a main.
  • Usamos runs-on: ubuntu-latest porque testes e lint não precisam de macOS (economia de custo).
  • cache: "yarn" faz o Actions reutilizar a pasta .yarn/cache, reduzindo tempo de CI de 3 min para 40 s na segunda execução.
  • codecov/codecov-action envia o relatório de cobertura; basta criar uma conta no Codecov e adicionar o token como secret CODECOV_TOKEN.

Passo 2 — Build de preview automático para Android

Agora que cada PR já tem qualidade garantida, vamos gerar um .apk para teste. Criamos o arquivo preview-android.yml:

name: Preview Android

on:
  workflow_run:
    workflows: ["PR Check"]
    types: [completed]
    branches-ignore: [main]

jobs:
  build-preview:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "yarn"
      - run: yarn install --frozen-lockfile
      - name: Build APK
        run: |
          cd android
          ./gradlew assembleRelease
      - name: Upload to Firebase App Distribution
        uses: wzieba/Firebase-Distribution-Github-Action@v1
        with:
          appId: ${{ secrets.FIREBASE_APP_ID_ANDROID }}
          serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }}
          groups: testers
          file: android/app/build/outputs/apk/release/app-release.apk

Dicas práticas

  • O trigger workflow_run espera o PR Check terminar; assim, não gastamos tempo se o código não passar nos testes.
  • Firebase App Distribution é gratuito para até 500 testers por app. Substitua por Expo EAS se preferir.

Passo 3 — Build de produção assinado (Android)

Para Play Store, precisamos de um .aab. Criamos release-android.yml:

name: Release Android

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "yarn"
      - run: yarn install --frozen-lockfile

      - name: Decode keystore
        run: |
          echo ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | base64 -d > android/app/my-upload.keystore
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: "temurin"
          java-version: "17"
      - name: Build AAB
        env:
          KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
        run: |
          cd android
          ./gradlew bundleRelease
      - name: Upload to Play Store
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
          packageName: com.minhaempresa.app
          releaseFiles: android/app/build/outputs/bundle/release/app-release.aab
          track: production
          status: completed

Segurança de chaves

  1. Crie um keystore localmente:
    keytool -genkey -v -keystore my-upload.keystore -alias upload -keyalg RSA -keysize 2048 -validity 10000
  2. Codifique em base64:
    base64 -w 0 my-upload.keystore
  3. Guarde o resultado em ANDROID_KEYSTORE_BASE64 (Settings > Secrets and Variables > Actions).

Passo 4 — Build iOS sem Mac próprio

Se você não tem um Mac, use o macos-latest do próprio GitHub. A parte chata é o gerenciamento de perfis e certificados. A comunidade criou a action yukiarrr/ios-build-action que abstrai a dor:

name: Release iOS

on:
  push:
    branches: [main]

jobs:
  release-ios:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "yarn"
      - run: yarn install --frozen-lockfile
      - name: Install pods
        run: cd ios && pod install
      - name: Build & sign
        uses: yukiarrr/[email protected]
        with:
          project-path: ios/MyApp.xcodeproj
          workspace-path: ios/MyApp.xcworkspace
          scheme: MyApp
          export-method: app-store
          configuration: Release
          team-id: ${{ secrets.IOS_TEAM_ID }}
          app-store-connect-api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}
          app-store-connect-api-key-issuer
Carregando publicação patrocinada...
0