name: Build # Manually triggered, parameterized build pipeline (build-only — no push/deploy). # # Trigger: GitHub → Actions tab → "Build" → "Run workflow" → pick: # environment: staging | prod (which flavor/config to build) # target: all | customer | mitra | backend | control_center # platform: android | ios | both (only affects the customer/mitra apps) # # Runner split: iOS app jobs run on macOS runners (iOS can't build on Linux); # everything else (Android, backend, control_center) runs on Linux. # # Outputs land as downloadable artifacts on the run summary page. # See .github/workflows/README.md for details. on: workflow_dispatch: inputs: environment: description: "Build environment" type: choice required: true default: staging options: [staging, prod] target: description: "What to build" type: choice required: true default: all options: [all, customer, mitra, backend, control_center] platform: description: "Mobile platform (apps only; macOS runners cost ~10x)" type: choice required: true default: android options: [android, ios, both] run-name: "Build ${{ inputs.target }} (${{ inputs.environment }}, ${{ inputs.platform }})" # Cancel an in-flight run of the same target+env+platform when a new one starts. concurrency: group: build-${{ inputs.target }}-${{ inputs.environment }}-${{ inputs.platform }} cancel-in-progress: true env: FLUTTER_VERSION: "3.41.9" # keep in sync with the team's local Flutter JAVA_VERSION: "17" # AGP 8 requires JDK 17 NODE_VERSION: "20" # matches backend engines + control_center jobs: # --------------------------------------------------------------------------- # Expand the `target` input into a JSON list the build jobs gate on. # `all` → every component; otherwise just the one chosen. # --------------------------------------------------------------------------- prepare: runs-on: ubuntu-latest outputs: targets: ${{ steps.expand.outputs.targets }} steps: - id: expand run: | if [ "${{ inputs.target }}" = "all" ]; then echo 'targets=["customer","mitra","backend","control_center"]' >> "$GITHUB_OUTPUT" else echo 'targets=["${{ inputs.target }}"]' >> "$GITHUB_OUTPUT" fi - run: echo "Building → ${{ steps.expand.outputs.targets }} (env=${{ inputs.environment }}, platform=${{ inputs.platform }})" # --------------------------------------------------------------------------- # customer app — ANDROID (Linux runner). Debug-signed (no keystore in repo). # --------------------------------------------------------------------------- customer-android: needs: prepare if: contains(fromJSON(needs.prepare.outputs.targets), 'customer') && (inputs.platform == 'android' || inputs.platform == 'both') runs-on: ubuntu-latest defaults: run: working-directory: client_app steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} - uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable cache: true - run: flutter pub get - name: Build APK run: | flutter build apk \ --flavor ${{ inputs.environment }} \ -t lib/main_${{ inputs.environment }}.dart \ --dart-define-from-file=env/${{ inputs.environment }}.json - uses: actions/upload-artifact@v4 with: name: customer-${{ inputs.environment }}-android-apk path: client_app/build/app/outputs/flutter-apk/app-${{ inputs.environment }}-release.apk if-no-files-found: error retention-days: 14 # --------------------------------------------------------------------------- # customer app — iOS (macOS runner). Unsigned build (--no-codesign). # PREREQ: per-flavor Xcode schemes must exist (staging/prod). Until then this # fails with "The Xcode project does not define custom schemes". Distributable # IPAs additionally need signing certs/profiles as secrets — not set up yet. # --------------------------------------------------------------------------- customer-ios: needs: prepare if: contains(fromJSON(needs.prepare.outputs.targets), 'customer') && (inputs.platform == 'ios' || inputs.platform == 'both') runs-on: macos-latest defaults: run: working-directory: client_app steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable cache: true - run: flutter pub get - name: Build iOS (unsigned) run: | flutter build ios --no-codesign \ --flavor ${{ inputs.environment }} \ -t lib/main_${{ inputs.environment }}.dart \ --dart-define-from-file=env/${{ inputs.environment }}.json - uses: actions/upload-artifact@v4 with: name: customer-${{ inputs.environment }}-ios-app path: client_app/build/ios/iphoneos/Runner.app if-no-files-found: error retention-days: 14 # --------------------------------------------------------------------------- # mitra app — ANDROID (Linux runner). # --------------------------------------------------------------------------- mitra-android: needs: prepare if: contains(fromJSON(needs.prepare.outputs.targets), 'mitra') && (inputs.platform == 'android' || inputs.platform == 'both') runs-on: ubuntu-latest defaults: run: working-directory: mitra_app steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} - uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable cache: true - run: flutter pub get - name: Build APK run: | flutter build apk \ --flavor ${{ inputs.environment }} \ -t lib/main_${{ inputs.environment }}.dart \ --dart-define-from-file=env/${{ inputs.environment }}.json - uses: actions/upload-artifact@v4 with: name: mitra-${{ inputs.environment }}-android-apk path: mitra_app/build/app/outputs/flutter-apk/app-${{ inputs.environment }}-release.apk if-no-files-found: error retention-days: 14 # --------------------------------------------------------------------------- # mitra app — iOS (macOS runner). Same PREREQ caveats as customer-ios. # --------------------------------------------------------------------------- mitra-ios: needs: prepare if: contains(fromJSON(needs.prepare.outputs.targets), 'mitra') && (inputs.platform == 'ios' || inputs.platform == 'both') runs-on: macos-latest defaults: run: working-directory: mitra_app steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} channel: stable cache: true - run: flutter pub get - name: Build iOS (unsigned) run: | flutter build ios --no-codesign \ --flavor ${{ inputs.environment }} \ -t lib/main_${{ inputs.environment }}.dart \ --dart-define-from-file=env/${{ inputs.environment }}.json - uses: actions/upload-artifact@v4 with: name: mitra-${{ inputs.environment }}-ios-app path: mitra_app/build/ios/iphoneos/Runner.app if-no-files-found: error retention-days: 14 # --------------------------------------------------------------------------- # control_center — Vite SPA static build (Linux). VITE_API_BASE_URL is baked # in at build time. Override defaults via repo Variables CC_API_BASE_URL_PROD # / CC_API_BASE_URL_STAGING (Settings → Secrets and variables → Actions). # --------------------------------------------------------------------------- control_center: needs: prepare if: contains(fromJSON(needs.prepare.outputs.targets), 'control_center') runs-on: ubuntu-latest defaults: run: working-directory: control_center env: VITE_API_BASE_URL: >- ${{ inputs.environment == 'prod' && (vars.CC_API_BASE_URL_PROD || 'https://internal.halobestie.com') || (vars.CC_API_BASE_URL_STAGING || 'https://staging-internal.halobestie.com') }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: npm cache-dependency-path: control_center/package-lock.json - run: npm ci - name: Build (VITE_API_BASE_URL=${{ env.VITE_API_BASE_URL }}) run: npm run build - uses: actions/upload-artifact@v4 with: name: control-center-${{ inputs.environment }}-dist path: control_center/dist if-no-files-found: error retention-days: 14 # --------------------------------------------------------------------------- # backend — Docker image (Linux). Env-AGNOSTIC (config is runtime env vars), # built once and exported as a loadable tarball. Deploy without a registry: # gunzip -c halobestie-backend-*.tar.gz | docker load # --------------------------------------------------------------------------- backend: needs: prepare if: contains(fromJSON(needs.prepare.outputs.targets), 'backend') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build image run: docker build -t halobestie-backend:${{ inputs.environment }} ./backend - name: Export image as tarball run: docker save halobestie-backend:${{ inputs.environment }} | gzip > halobestie-backend-${{ inputs.environment }}.tar.gz - uses: actions/upload-artifact@v4 with: name: backend-${{ inputs.environment }}-image path: halobestie-backend-${{ inputs.environment }}.tar.gz if-no-files-found: error retention-days: 14