From 639ae8475136dfe7423c27f15087e4dacade7ee9 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Tue, 9 Dec 2025 16:27:30 +0700 Subject: [PATCH 01/13] feat: add Docker configuration for Android build - Add multi-stage Dockerfile for Android APK build - Configure Flutter 3.38.4 and Android SDK 35 - Add .dockerignore for build optimization - Support for armeabi-v7a, arm64-v8a, x86_64 architectures - Update .gitignore to include Docker & APK related files --- .dockerignore | 114 ++++++++++++++++++++++++++++++++ .gitignore | 11 ++- CHANGELOG.md | 4 ++ Dockerfile | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7eb3709 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,114 @@ +# Flutter/Dart related +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +*.lock.json +.metadata + +# Android related +android/.gradle/ +android/app/build/ +android/gradle/ +android/.idea/ +android/local.properties +android/key.properties +android/app/release/ +android/app/debug/ +android/captures/ +android/*.iml +*.jks +*.keystore +*.p12 +google-services.json + +# iOS related +ios/Pods/ +ios/.symlinks/ +ios/Flutter/Flutter.framework +ios/Flutter/Flutter.podspec +ios/.generated/ +ios/Runner.xcworkspace/ +ios/Runner.xcodeproj/xcuserdata/ +ios/Runner.xcodeproj/project.xcworkspace/xcuserdata/ +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +*.xcuserstate +xcuserdata/ +DerivedData/ +*.mobileprovision +*.cer +*.p12 +GoogleService-Info.plist + +# IDE related +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db +desktop.ini + +# Git +.git/ +.gitignore +.gitattributes +.github/ + +# CI/CD +.gitlab-ci.yml +.circleci/ +.travis.yml +azure-pipelines.yml + +# Testing and coverage +coverage/ +test/ +integration_test/ +.test_coverage/ +*.lcov +*.info + +# Documentation +README.md +README*.md +LICENSE +CHANGELOG.md +docs/ +CONTRIBUTING.md + +# Docker files +Dockerfile +Dockerfile.* +.dockerignore +docker-compose.yml +docker-compose.*.yml + +# Local configurations +.env +.env.* +.env.local +.env.*.local +*.log +*.log.* + +# Temporary files +*.tmp +*.temp +*.bak +*.swp +*~ + +# Firebase +firebase.json +.firebaserc +firestore.rules +firestore.indexes.json +storage.rules diff --git a/.gitignore b/.gitignore index d540810..3421833 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,15 @@ unlinked_spec.ds # Firebase related firebase_options.dart +# Docker related +*.img +*.tar +*.tar.gz + +# APK related +*.apk +apk-output/ + # Symbolication related app.*.symbols @@ -130,4 +139,4 @@ ios/Runner/GoogleService-Info.plist android/app/google-services.json # Generated localization -lib/core/localization/generated/ \ No newline at end of file +lib/core/localization/generated/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cbd39b..170946f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added DI-independent fallback error handler inside runZonedGuarded to ensure reliable error reporting even if DI or Firebase are not ready. - Added unified analytics architecture with `Firebase Analytics` integration, debug logging, and automatic screen view tracking via `AnalyticsRouteObserver`. - Added `AppLoggerMixin` for convenient logger access in Bloc/Cubit +- **CI/CD**: Docker configuration for Android build pipeline + - Multi-stage Dockerfile with Flutter 3.38.4 and Android SDK 35 + - Support for split APK by ABI (armeabi-v7a, arm64-v8a, x86_64) + - `.dockerignore` for optimized build context ### Changed diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4b784a6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,180 @@ +# ========================================== +# Global ARGs +# ========================================== +ARG TARGETPLATFORM=linux/amd64 +ARG FLUTTER_VERSION=3.38.4 +ARG ANDROID_SDK_VERSION=35 +ARG ANDROID_BUILD_TOOLS_VERSION=35.0.0 +ARG CMDLINE_TOOLS_VERSION=13114758 + + +# ========================================== +# Stage 1: Builder - Android APK build +# ========================================== +FROM --platform=$TARGETPLATFORM ubuntu:22.04 AS builder + + +# Use global ARGs +ARG FLUTTER_VERSION +ARG ANDROID_SDK_VERSION +ARG ANDROID_BUILD_TOOLS_VERSION +ARG CMDLINE_TOOLS_VERSION + + +# Disable interactive prompts +ENV DEBIAN_FRONTEND=noninteractive + + +# Install required dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + unzip \ + xz-utils \ + zip \ + libglu1-mesa \ + openjdk-17-jdk \ + wget \ + ca-certificates \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + + +# Detect actual Java path and create symlink if needed +RUN REAL_JAVA=$(dirname $(dirname $(readlink -f $(which javac)))) && \ + echo "Real Java path: ${REAL_JAVA}" && \ + if [ "${REAL_JAVA}" != "/usr/lib/jvm/java-17-openjdk-amd64" ]; then \ + mkdir -p /usr/lib/jvm && \ + ln -sf ${REAL_JAVA} /usr/lib/jvm/java-17-openjdk-amd64; \ + fi + + +# Set JAVA_HOME via ENV (works for all subsequent RUN commands) +ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +ENV PATH=$JAVA_HOME/bin:$PATH + + +# Verify that Java is working +RUN java -version && javac -version && \ + echo "JAVA_HOME is set to: ${JAVA_HOME}" && \ + ls -la ${JAVA_HOME}/bin/java + + +# Configure Android SDK +ENV ANDROID_SDK_ROOT=/opt/android-sdk +ENV ANDROID_HOME=$ANDROID_SDK_ROOT +ENV PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/build-tools/${ANDROID_BUILD_TOOLS_VERSION} + + +# Create directory for Android SDK +RUN mkdir -p $ANDROID_SDK_ROOT/cmdline-tools + + +# Download and install Android Command Line Tools +RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip -O /tmp/cmdline-tools.zip && \ + unzip -q /tmp/cmdline-tools.zip -d $ANDROID_SDK_ROOT/cmdline-tools && \ + mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest && \ + rm /tmp/cmdline-tools.zip + + +# Install required Android SDK components (JAVA_HOME already set via ENV) +RUN echo "Current JAVA_HOME: ${JAVA_HOME}" && \ + yes | sdkmanager --licenses && \ + sdkmanager --install \ + "platform-tools" \ + "platforms;android-${ANDROID_SDK_VERSION}" \ + "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ + "cmdline-tools;latest" + + +# Download Flutter SDK (release archive instead of git clone for speed) +RUN wget -q https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz -O /tmp/flutter.tar.xz && \ + tar xf /tmp/flutter.tar.xz -C /opt && \ + rm /tmp/flutter.tar.xz + + +# Configure environment variables for Flutter +ENV FLUTTER_ROOT=/opt/flutter +ENV PATH="${FLUTTER_ROOT}/bin:${FLUTTER_ROOT}/bin/cache/dart-sdk/bin:$PATH" +ENV PUB_CACHE=/root/.pub-cache + + +# Fix Git safe.directory warning +RUN git config --global --add safe.directory /opt/flutter && \ + git config --global --add safe.directory "${FLUTTER_ROOT}" + + +# Initial Flutter setup +RUN flutter doctor -v && \ + flutter config --no-analytics && \ + flutter config --android-sdk $ANDROID_SDK_ROOT && \ + flutter precache --android --no-ios --no-web + + +# Create directories for caching +RUN mkdir -p $PUB_CACHE && \ + mkdir -p /root/.gradle + + +# Set working directory +WORKDIR /app + + +# Copy dependency files to leverage layer caching +COPY pubspec.yaml pubspec.lock ./ + + +# Install dependencies +RUN flutter pub get + + +# Copy entire project +COPY . . + + +# Build Android APK in release mode +RUN flutter build apk --release --split-per-abi --no-tree-shake-icons + + +# Verify that the build succeeded +RUN test -f /app/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk || \ + test -f /app/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk || \ + (echo "ERROR: APK files not found!" && exit 1) + + +# ========================================== +# Stage 2: Runtime - Artifact storage +# ========================================== +FROM alpine:latest AS runtime + + +# Set image metadata +ARG FLUTTER_VERSION +ARG BUILD_DATE +ARG VCS_REF + + +LABEL maintainer="cowboycloud@icloud.com" \ + description="Flutter Android APK Build Artifacts" \ + version="1.0.0" \ + flutter.version="${FLUTTER_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.revision="${VCS_REF}" \ + org.opencontainers.image.title="Flutter Starter Template Android Build" \ + org.opencontainers.image.description="Production-ready Android APK artifacts" + + +# Create directory for APKs +RUN mkdir -p /artifacts + + +# Copy built APKs from builder stage +COPY --from=builder /app/build/app/outputs/flutter-apk/*.apk /artifacts/ + + +# Set working directory +WORKDIR /artifacts + + +# Default command - list files with sizes +CMD ["ls", "-lh"] From e4e51a164d8fb81b771ecc255f5dc174136a1a1c Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Tue, 9 Dec 2025 16:30:45 +0700 Subject: [PATCH 02/13] feat: add complete CI/CD workflow with Docker and iOS build - Add code analysis job with formatting, analyze, and tests - Add Android build job with Docker and DockerHub integration - Add iOS build job with macOS runner - Configure GitHub Actions cache for faster builds --- .github/workflows/main.yml | 258 +++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 4 +- 2 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6a8c594 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,258 @@ +name: Flutter CI/CD Pipeline + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + workflow_dispatch: + +env: + FLUTTER_VERSION: '3.38.4' + ANDROID_SDK_VERSION: '35' + +jobs: + # ========================================== + # Job 1: Code Quality & Analysis + # ========================================== + analyze: + name: Code Analysis + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: 'stable' + cache: true + + - name: Get dependencies + run: flutter pub get + + - name: Verify formatting + run: dart format --set-exit-if-changed lib/ test/ + continue-on-error: false + + - name: Analyze code + run: flutter analyze --fatal-infos + + - name: Run tests + run: flutter test --coverage || echo "⚠️ No tests found" + + - name: Upload coverage to artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: code-coverage + path: coverage/lcov.info + if-no-files-found: ignore + retention-days: 7 + + # ========================================== + # Job 2: Android Build with Docker + # ========================================== + build-android: + name: Build Android APK + runs-on: ubuntu-latest + needs: analyze + timeout-minutes: 60 + + outputs: + image-tag: ${{ steps.meta.outputs.tags }} + image-digest: ${{ steps.build.outputs.digest }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKERHUB_USERNAME }}/flutter-starter-android + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha,prefix={{branch}}-,format=short + type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64 + push: ${{ github.event_name != 'pull_request' }} + load: ${{ github.event_name == 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + BUILD_DATE=${{ github.event.repository.updated_at }} + VCS_REF=${{ github.sha }} + FLUTTER_VERSION=${{ env.FLUTTER_VERSION }} + ANDROID_SDK_VERSION=${{ env.ANDROID_SDK_VERSION }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Extract APK from Docker image + run: | + IMAGE_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n1) + echo "Using image: ${IMAGE_TAG}" + + docker create --name temp-flutter "${IMAGE_TAG}" + mkdir -p apk-output + docker cp temp-flutter:/artifacts/. apk-output/ + docker rm temp-flutter + + if [ ! "$(ls -A apk-output/*.apk 2>/dev/null)" ]; then + echo "ERROR: No APK files found in apk-output/" + exit 1 + fi + + echo "Successfully extracted APK files:" + ls -lh apk-output/ + + - name: Upload Android APK artifacts + uses: actions/upload-artifact@v4 + with: + name: android-apk-${{ github.sha }} + path: apk-output/*.apk + retention-days: 30 + compression-level: 0 + + - name: Generate build summary + run: | + echo "## 🤖 Android APK Build Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Build Info:**" >> $GITHUB_STEP_SUMMARY + echo "- Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Branch: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Flutter: \`${{ env.FLUTTER_VERSION }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Android SDK: \`${{ env.ANDROID_SDK_VERSION }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Architecture | Filename | Size |" >> $GITHUB_STEP_SUMMARY + echo "|--------------|----------|------|" >> $GITHUB_STEP_SUMMARY + + for apk in apk-output/*.apk; do + if [ -f "$apk" ]; then + filename=$(basename "$apk") + size=$(du -h "$apk" | cut -f1) + + if [[ "$filename" == *"arm64-v8a"* ]]; then + arch="ARM64 (64-bit)" + elif [[ "$filename" == *"armeabi-v7a"* ]]; then + arch="ARMv7 (32-bit)" + elif [[ "$filename" == *"x86_64"* ]]; then + arch="x86_64" + else + arch="Universal" + fi + + echo "| $arch | \`$filename\` | $size |" >> $GITHUB_STEP_SUMMARY + fi + done + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ github.event_name }}" != "pull_request" ]; then + echo "🐳 **Docker Image:** \`${{ steps.meta.outputs.tags }}\`" >> $GITHUB_STEP_SUMMARY + else + echo "ℹ️ *Docker image not pushed (Pull Request build)*" >> $GITHUB_STEP_SUMMARY + fi + + # ========================================== + # Job 3: iOS Build (macOS runner) + # ========================================== + build-ios: + name: Build iOS IPA + runs-on: macos-latest + needs: analyze + timeout-minutes: 45 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: 'stable' + cache: true + + - name: Get dependencies + run: flutter pub get + + - name: Install iOS dependencies + run: | + cd ios + pod install || echo "No Podfile found, skipping pod install" + cd .. + + - name: Build iOS app (no codesign) + run: | + flutter build ios --release --no-codesign \ + --build-name=1.0.0 \ + --build-number=${{ github.run_number }} + + - name: Create unsigned IPA + run: | + mkdir -p Payload + cp -R build/ios/iphoneos/Runner.app Payload/ + + IPA_NAME="flutter-starter-ios-${{ github.run_number }}-unsigned.ipa" + zip -r "$IPA_NAME" Payload + + rm -rf Payload + + echo "IPA created: $IPA_NAME" + ls -lh *.ipa + + - name: Upload iOS IPA artifact + uses: actions/upload-artifact@v4 + with: + name: ios-ipa-${{ github.sha }} + path: "*.ipa" + retention-days: 30 + compression-level: 0 + if-no-files-found: error + + + - name: Generate iOS build summary + run: | + echo "## 🍎 iOS IPA Build Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Build Info:**" >> $GITHUB_STEP_SUMMARY + echo "- Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Branch: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Flutter: \`${{ env.FLUTTER_VERSION }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Build Number: \`${{ github.run_number }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| File | Size |" >> $GITHUB_STEP_SUMMARY + echo "|------|------|" >> $GITHUB_STEP_SUMMARY + + for ipa in *.ipa; do + if [ -f "$ipa" ]; then + size=$(du -h "$ipa" | cut -f1) + echo "| \`$ipa\` | $size |" >> $GITHUB_STEP_SUMMARY + fi + done + + echo "" >> $GITHUB_STEP_SUMMARY + echo "⚠️ **Note**: This is an unsigned IPA for testing purposes only." >> $GITHUB_STEP_SUMMARY + echo "For App Store distribution, code signing is required." >> $GITHUB_STEP_SUMMARY diff --git a/CHANGELOG.md b/CHANGELOG.md index 170946f..f2e3dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,10 +24,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added DI-independent fallback error handler inside runZonedGuarded to ensure reliable error reporting even if DI or Firebase are not ready. - Added unified analytics architecture with `Firebase Analytics` integration, debug logging, and automatic screen view tracking via `AnalyticsRouteObserver`. - Added `AppLoggerMixin` for convenient logger access in Bloc/Cubit -- **CI/CD**: Docker configuration for Android build pipeline +- **CI/CD**: Complete automated build pipeline with GitHub Actions - Multi-stage Dockerfile with Flutter 3.38.4 and Android SDK 35 - Support for split APK by ABI (armeabi-v7a, arm64-v8a, x86_64) - `.dockerignore` for optimized build context + - GitHub Actions workflow: code analysis, Android (Docker + DockerHub), iOS (macOS runner) + - Automated quality checks, artifact uploads, and build summaries ### Changed From b3cfc44fd10bd8c54b18719903a453a5543b49bb Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Tue, 9 Dec 2025 16:48:24 +0700 Subject: [PATCH 03/13] chore: ignore generated build_runner files (*.g.dart, *.mocks.dart) --- .gitignore | 4 ++ .../analytics_route_onserver_test.mocks.dart | 47 ------------------- 2 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 test/core/router/analytics_route_onserver_test.mocks.dart diff --git a/.gitignore b/.gitignore index 3421833..c539165 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,10 @@ linked_*.ds unlinked.ds unlinked_spec.ds +# Build Runner generated +*.g.dart +*.mocks.dart + # Firebase related firebase_options.dart diff --git a/test/core/router/analytics_route_onserver_test.mocks.dart b/test/core/router/analytics_route_onserver_test.mocks.dart deleted file mode 100644 index 6968f91..0000000 --- a/test/core/router/analytics_route_onserver_test.mocks.dart +++ /dev/null @@ -1,47 +0,0 @@ -// Mocks generated by Mockito 5.4.6 from annotations -// in flutter_starter_template/test/core/router/analytics_route_onserver_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i3; - -import 'package:flutter_starter_template/core/utils/analytics/app_analytics.dart' - as _i2; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: must_be_immutable -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -/// A class which mocks [AppAnalytics]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockAppAnalytics extends _i1.Mock implements _i2.AppAnalytics { - @override - _i3.Future logEvent(String? name, {Map? parameters}) => - (super.noSuchMethod( - Invocation.method(#logEvent, [name], {#parameters: parameters}), - returnValue: _i3.Future.value(), - returnValueForMissingStub: _i3.Future.value(), - ) - as _i3.Future); - - @override - _i3.Future logScreenView(String? screenName) => - (super.noSuchMethod( - Invocation.method(#logScreenView, [screenName]), - returnValue: _i3.Future.value(), - returnValueForMissingStub: _i3.Future.value(), - ) - as _i3.Future); -} From 6bbe4358d0d9f490bfa413093552d241fcd47fc9 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Tue, 9 Dec 2025 16:49:24 +0700 Subject: [PATCH 04/13] feat: add build_runner step to CI for code generation --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a8c594..d2903d1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,6 +32,9 @@ jobs: - name: Get dependencies run: flutter pub get + + - name: Run build_runner + run: dart run build_runner build --delete-conflicting-outputs - name: Verify formatting run: dart format --set-exit-if-changed lib/ test/ From d9a31db04d4117f0c1ddc33c429611f6399f9d52 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Tue, 9 Dec 2025 16:59:47 +0700 Subject: [PATCH 05/13] feat: add code formatting step after build_runner in CI workflow --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d2903d1..2976157 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,9 @@ jobs: - name: Run build_runner run: dart run build_runner build --delete-conflicting-outputs + + - name: Format generated code + run: dart format . - name: Verify formatting run: dart format --set-exit-if-changed lib/ test/ From fcb135b22b163630f722f15178cb16f7e1123b48 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Tue, 9 Dec 2025 17:09:24 +0700 Subject: [PATCH 06/13] fix: add step to create fake firebase_options.dart for Firebase configuration --- .github/workflows/main.yml | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2976157..f670821 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,6 +30,67 @@ jobs: channel: 'stable' cache: true + + # Добавь этот шаг + - name: Create fake firebase_options.dart + run: | + mkdir -p lib + cat < lib/firebase_options.dart + import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; + import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; + + class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'fake-api-key', + appId: 'fake-app-id', + messagingSenderId: 'fake-sender-id', + projectId: 'fake-project-id', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'fake-api-key', + appId: 'fake-app-id', + messagingSenderId: 'fake-sender-id', + projectId: 'fake-project-id', + storageBucket: 'fake-bucket', + ); + } + EOF + - name: Get dependencies run: flutter pub get From 717e15c139cde3535b94013bde616b19267f2cf4 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Tue, 9 Dec 2025 17:18:18 +0700 Subject: [PATCH 07/13] fix: update fake firebase_options.dart --- .github/workflows/main.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f670821..6344fa6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,8 +30,6 @@ jobs: channel: 'stable' cache: true - - # Добавь этот шаг - name: Create fake firebase_options.dart run: | mkdir -p lib @@ -39,7 +37,9 @@ jobs: import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; + /// class DefaultFirebaseOptions { + /// static FirebaseOptions get currentPlatform { if (kIsWeb) { throw UnsupportedError( @@ -54,7 +54,7 @@ jobs: return ios; case TargetPlatform.macOS: throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for windows - ' + 'DefaultFirebaseOptions have not been configured for macos - ' 'you can reconfigure this by running the FlutterFire CLI again.', ); case TargetPlatform.windows: @@ -74,21 +74,26 @@ jobs: } } - static const FirebaseOptions web = FirebaseOptions( + /// + static const FirebaseOptions android = FirebaseOptions( apiKey: 'fake-api-key', appId: 'fake-app-id', messagingSenderId: 'fake-sender-id', projectId: 'fake-project-id', + storageBucket: 'fake-bucket', ); - static const FirebaseOptions android = FirebaseOptions( + /// + static const FirebaseOptions ios = FirebaseOptions( apiKey: 'fake-api-key', appId: 'fake-app-id', messagingSenderId: 'fake-sender-id', projectId: 'fake-project-id', storageBucket: 'fake-bucket', + iosBundleId: 'fake-bundle-id', ); } + EOF - name: Get dependencies From 48cac1fa203edb000f9d221bae10fb5bd7af6f8e Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Wed, 10 Dec 2025 17:49:55 +0700 Subject: [PATCH 08/13] fix: update Docker tags format and add fake firebase_options in CI --- .github/workflows/main.yml | 100 ++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 17 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6344fa6..d0897e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,21 +52,6 @@ jobs: return android; case TargetPlatform.iOS: return ios; - case TargetPlatform.macOS: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for macos - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); - case TargetPlatform.windows: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for windows - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); - case TargetPlatform.linux: - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for linux - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); default: throw UnsupportedError( 'DefaultFirebaseOptions are not supported for this platform.', @@ -93,7 +78,6 @@ jobs: iosBundleId: 'fake-bundle-id', ); } - EOF - name: Get dependencies @@ -159,11 +143,52 @@ jobs: tags: | type=ref,event=branch type=ref,event=pr - type=sha,prefix={{branch}}-,format=short + type=sha,format=short,prefix=sha- type=raw,value=latest,enable={{is_default_branch}} type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} + - name: Create fake firebase_options.dart + run: | + mkdir -p lib + cat < lib/firebase_options.dart + import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; + import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; + + class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError('Web not configured'); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + default: + throw UnsupportedError('Platform not configured'); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'fake-api-key', + appId: 'fake-app-id', + messagingSenderId: 'fake-sender-id', + projectId: 'fake-project-id', + storageBucket: 'fake-bucket', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'fake-api-key', + appId: 'fake-app-id', + messagingSenderId: 'fake-sender-id', + projectId: 'fake-project-id', + storageBucket: 'fake-bucket', + iosBundleId: 'fake-bundle-id', + ); + } + EOF + - name: Build and push Docker image id: build uses: docker/build-push-action@v5 @@ -267,6 +292,47 @@ jobs: channel: 'stable' cache: true + - name: Create fake firebase_options.dart + run: | + mkdir -p lib + cat < lib/firebase_options.dart + import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; + import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; + + class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError('Web not configured'); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + default: + throw UnsupportedError('Platform not configured'); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'fake-api-key', + appId: 'fake-app-id', + messagingSenderId: 'fake-sender-id', + projectId: 'fake-project-id', + storageBucket: 'fake-bucket', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'fake-api-key', + appId: 'fake-app-id', + messagingSenderId: 'fake-sender-id', + projectId: 'fake-project-id', + storageBucket: 'fake-bucket', + iosBundleId: 'fake-bundle-id', + ); + } + EOF + - name: Get dependencies run: flutter pub get From 2f9fcc3e4afb01414232551d160696683f670e60 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Wed, 10 Dec 2025 18:39:56 +0700 Subject: [PATCH 09/13] fix: add fake google-services.json and GoogleService-Info.plist for Firebase configuration and update configuration file creation steps --- .github/workflows/main.yml | 73 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d0897e4..0edfc47 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -148,8 +148,9 @@ jobs: type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - - name: Create fake firebase_options.dart + - name: Create configuration files run: | + # Create firebase_options.dart mkdir -p lib cat < lib/firebase_options.dart import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; @@ -188,6 +189,39 @@ jobs: ); } EOF + + # google-services.json + cat < android/app/google-services.json + { + "project_info": { + "project_number": "123456789012", + "project_id": "fake-project-id", + "storage_bucket": "fake-project-id.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:123456789012:android:321abc456def7890", + "android_client_info": { + "package_name": "com.flutter.starter.template.flutter_starter_template" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "fake-api-key" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" + } + EOF - name: Build and push Docker image id: build @@ -292,8 +326,9 @@ jobs: channel: 'stable' cache: true - - name: Create fake firebase_options.dart + - name: Create configuration files run: | + # Create firebase_options.dart mkdir -p lib cat < lib/firebase_options.dart import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; @@ -333,6 +368,40 @@ jobs: } EOF + # Create GoogleService-Info.plist + cat < ios/Runner/GoogleService-Info.plist + + + + + API_KEY + fake-api-key + GCM_SENDER_ID + 123456789012 + PLIST_VERSION + 1 + BUNDLE_ID + com.flutter.starter.template.flutterStarterTemplate + PROJECT_ID + fake-project-id + STORAGE_BUCKET + fake-project-id.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:123456789012:ios:321abc456def7890 + + + EOF + - name: Get dependencies run: flutter pub get From 16d3d411694026ab87f4ddd112cfe7a43a5ebde8 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Thu, 11 Dec 2025 08:47:08 +0700 Subject: [PATCH 10/13] fix: update iOS dependency installation to remove existing Pods and Podfile.lock before reinstalling --- .github/workflows/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0edfc47..0acd875 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -408,12 +408,15 @@ jobs: - name: Install iOS dependencies run: | cd ios - pod install || echo "No Podfile found, skipping pod install" + rm -rf Pods + rm -rf Podfile.lock + # Устанавливаем заново + pod install --repo-update cd .. - name: Build iOS app (no codesign) run: | - flutter build ios --release --no-codesign \ + flutter build ios --release --no-codesign --verbose \ --build-name=1.0.0 \ --build-number=${{ github.run_number }} From e3836417dd8518cd84a77ec53f1f51489168f1f1 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Thu, 11 Dec 2025 09:21:53 +0700 Subject: [PATCH 11/13] fix: remove unused FlutterFire upload symbols script from iOS build phase and remove unused outputs from Android job --- .github/workflows/main.yml | 4 ---- ios/Runner.xcodeproj/project.pbxproj | 25 +++++-------------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0acd875..ae18342 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -117,10 +117,6 @@ jobs: needs: analyze timeout-minutes: 60 - outputs: - image-tag: ${{ steps.meta.outputs.tags }} - image-digest: ${{ steps.build.outputs.digest }} - steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 86ec58e..0439783 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -201,7 +201,6 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3D0E01DF18316EDF773EAB0D /* [CP] Embed Pods Frameworks */, - 05D396F55CC39F0934932997 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */, ); buildRules = ( ); @@ -274,24 +273,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 05D396F55CC39F0934932997 /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\""; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n"; - }; 1D144EDA24AD260BDCD8A5DC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -360,10 +341,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -382,7 +367,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; /* End PBXShellScriptBuildPhase section */ From 174d346030c2898d74a7bc358a7a63eb4edf219d Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Fri, 12 Dec 2025 18:44:21 +0700 Subject: [PATCH 12/13] chore: reduce and optimize pipeline --- .github/workflows/main.yml | 337 +++++-------------------------------- 1 file changed, 44 insertions(+), 293 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae18342..223578d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,49 +34,19 @@ jobs: run: | mkdir -p lib cat < lib/firebase_options.dart - import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; - import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; - - /// + // ignore_for_file: type=lint + import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; class DefaultFirebaseOptions { - /// static FirebaseOptions get currentPlatform { - if (kIsWeb) { - throw UnsupportedError( - 'DefaultFirebaseOptions have not been configured for web - ' - 'you can reconfigure this by running the FlutterFire CLI again.', - ); - } + if (kIsWeb) throw UnsupportedError('Web not configured'); switch (defaultTargetPlatform) { - case TargetPlatform.android: - return android; - case TargetPlatform.iOS: - return ios; - default: - throw UnsupportedError( - 'DefaultFirebaseOptions are not supported for this platform.', - ); + case TargetPlatform.android: return android; + case TargetPlatform.iOS: return ios; + default: throw UnsupportedError('Platform not configured'); } } - - /// - static const FirebaseOptions android = FirebaseOptions( - apiKey: 'fake-api-key', - appId: 'fake-app-id', - messagingSenderId: 'fake-sender-id', - projectId: 'fake-project-id', - storageBucket: 'fake-bucket', - ); - - /// - static const FirebaseOptions ios = FirebaseOptions( - apiKey: 'fake-api-key', - appId: 'fake-app-id', - messagingSenderId: 'fake-sender-id', - projectId: 'fake-project-id', - storageBucket: 'fake-bucket', - iosBundleId: 'fake-bundle-id', - ); + static const FirebaseOptions android = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake'); + static const FirebaseOptions ios = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake', iosBundleId: 'fake'); } EOF @@ -86,9 +56,6 @@ jobs: - name: Run build_runner run: dart run build_runner build --delete-conflicting-outputs - - name: Format generated code - run: dart format . - - name: Verify formatting run: dart format --set-exit-if-changed lib/ test/ continue-on-error: false @@ -109,7 +76,7 @@ jobs: retention-days: 7 # ========================================== - # Job 2: Android Build with Docker + # Job 2: Android Build (Docker) # ========================================== build-android: name: Build Android APK @@ -124,111 +91,22 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to DockerHub - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ secrets.DOCKERHUB_USERNAME }}/flutter-starter-android - tags: | - type=ref,event=branch - type=ref,event=pr - type=sha,format=short,prefix=sha- - type=raw,value=latest,enable={{is_default_branch}} - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Create configuration files + - name: Create mock configuration files run: | - # Create firebase_options.dart mkdir -p lib - cat < lib/firebase_options.dart - import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; - import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; - - class DefaultFirebaseOptions { - static FirebaseOptions get currentPlatform { - if (kIsWeb) { - throw UnsupportedError('Web not configured'); - } - switch (defaultTargetPlatform) { - case TargetPlatform.android: - return android; - case TargetPlatform.iOS: - return ios; - default: - throw UnsupportedError('Platform not configured'); - } - } - - static const FirebaseOptions android = FirebaseOptions( - apiKey: 'fake-api-key', - appId: 'fake-app-id', - messagingSenderId: 'fake-sender-id', - projectId: 'fake-project-id', - storageBucket: 'fake-bucket', - ); - - static const FirebaseOptions ios = FirebaseOptions( - apiKey: 'fake-api-key', - appId: 'fake-app-id', - messagingSenderId: 'fake-sender-id', - projectId: 'fake-project-id', - storageBucket: 'fake-bucket', - iosBundleId: 'fake-bundle-id', - ); - } - EOF - + # firebase_options.dart + echo "import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) throw UnsupportedError('Web not configured'); switch (defaultTargetPlatform) { case TargetPlatform.android: return android; case TargetPlatform.iOS: return ios; default: throw UnsupportedError('Platform not configured'); } } static const FirebaseOptions android = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake'); static const FirebaseOptions ios = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake', iosBundleId: 'fake'); }" > lib/firebase_options.dart # google-services.json - cat < android/app/google-services.json - { - "project_info": { - "project_number": "123456789012", - "project_id": "fake-project-id", - "storage_bucket": "fake-project-id.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:123456789012:android:321abc456def7890", - "android_client_info": { - "package_name": "com.flutter.starter.template.flutter_starter_template" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "fake-api-key" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [] - } - } - } - ], - "configuration_version": "1" - } - EOF + echo '{"project_info":{"project_number":"123","project_id":"fake","storage_bucket":"fake"},"client":[{"client_info":{"mobilesdk_app_id":"1:123:android:123","android_client_info":{"package_name":"com.flutter.starter.template.flutter_starter_template"}},"api_key":[{"current_key":"fake"}]}]}' > android/app/google-services.json - - name: Build and push Docker image + - name: Build Docker image id: build uses: docker/build-push-action@v5 with: context: . - platforms: linux/amd64 - push: ${{ github.event_name != 'pull_request' }} - load: ${{ github.event_name == 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + push: false + load: true + tags: flutter-build:latest build-args: | BUILD_DATE=${{ github.event.repository.updated_at }} VCS_REF=${{ github.sha }} @@ -237,22 +115,12 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Extract APK from Docker image + - name: Extract APK run: | - IMAGE_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n1) - echo "Using image: ${IMAGE_TAG}" - - docker create --name temp-flutter "${IMAGE_TAG}" + docker create --name temp flutter-build:latest mkdir -p apk-output - docker cp temp-flutter:/artifacts/. apk-output/ - docker rm temp-flutter - - if [ ! "$(ls -A apk-output/*.apk 2>/dev/null)" ]; then - echo "ERROR: No APK files found in apk-output/" - exit 1 - fi - - echo "Successfully extracted APK files:" + docker cp temp:/artifacts/. apk-output/ + docker rm temp ls -lh apk-output/ - name: Upload Android APK artifacts @@ -263,44 +131,20 @@ jobs: retention-days: 30 compression-level: 0 - - name: Generate build summary + - name: Generate Summary run: | echo "## 🤖 Android APK Build Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Build Info:**" >> $GITHUB_STEP_SUMMARY - echo "- Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Branch: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Flutter: \`${{ env.FLUTTER_VERSION }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Android SDK: \`${{ env.ANDROID_SDK_VERSION }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Architecture | Filename | Size |" >> $GITHUB_STEP_SUMMARY + echo "| Architecture | File | Size |" >> $GITHUB_STEP_SUMMARY echo "|--------------|----------|------|" >> $GITHUB_STEP_SUMMARY - for apk in apk-output/*.apk; do - if [ -f "$apk" ]; then - filename=$(basename "$apk") - size=$(du -h "$apk" | cut -f1) - - if [[ "$filename" == *"arm64-v8a"* ]]; then - arch="ARM64 (64-bit)" - elif [[ "$filename" == *"armeabi-v7a"* ]]; then - arch="ARMv7 (32-bit)" - elif [[ "$filename" == *"x86_64"* ]]; then - arch="x86_64" - else - arch="Universal" - fi - - echo "| $arch | \`$filename\` | $size |" >> $GITHUB_STEP_SUMMARY - fi + name=$(basename "$apk") + size=$(du -h "$apk" | cut -f1) + arch="Universal" + if [[ "$name" == *"arm64"* ]]; then arch="ARM64"; fi + if [[ "$name" == *"armeabi"* ]]; then arch="ARMv7"; fi + if [[ "$name" == *"x86_64"* ]]; then arch="x86_64"; fi + echo "| $arch | \`$name\` | $size |" >> $GITHUB_STEP_SUMMARY done - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${{ github.event_name }}" != "pull_request" ]; then - echo "🐳 **Docker Image:** \`${{ steps.meta.outputs.tags }}\`" >> $GITHUB_STEP_SUMMARY - else - echo "ℹ️ *Docker image not pushed (Pull Request build)*" >> $GITHUB_STEP_SUMMARY - fi # ========================================== # Job 3: iOS Build (macOS runner) @@ -322,112 +166,34 @@ jobs: channel: 'stable' cache: true - - name: Create configuration files + - name: Create mock configuration files run: | + mkdir -p lib ios/Runner # Create firebase_options.dart - mkdir -p lib - cat < lib/firebase_options.dart - import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; - import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; - - class DefaultFirebaseOptions { - static FirebaseOptions get currentPlatform { - if (kIsWeb) { - throw UnsupportedError('Web not configured'); - } - switch (defaultTargetPlatform) { - case TargetPlatform.android: - return android; - case TargetPlatform.iOS: - return ios; - default: - throw UnsupportedError('Platform not configured'); - } - } - - static const FirebaseOptions android = FirebaseOptions( - apiKey: 'fake-api-key', - appId: 'fake-app-id', - messagingSenderId: 'fake-sender-id', - projectId: 'fake-project-id', - storageBucket: 'fake-bucket', - ); - - static const FirebaseOptions ios = FirebaseOptions( - apiKey: 'fake-api-key', - appId: 'fake-app-id', - messagingSenderId: 'fake-sender-id', - projectId: 'fake-project-id', - storageBucket: 'fake-bucket', - iosBundleId: 'fake-bundle-id', - ); - } - EOF - + echo "import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) throw UnsupportedError('Web not configured'); switch (defaultTargetPlatform) { case TargetPlatform.android: return android; case TargetPlatform.iOS: return ios; default: throw UnsupportedError('Platform not configured'); } } static const FirebaseOptions android = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake'); static const FirebaseOptions ios = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake', iosBundleId: 'fake'); }" > lib/firebase_options.dart # Create GoogleService-Info.plist - cat < ios/Runner/GoogleService-Info.plist - - - - - API_KEY - fake-api-key - GCM_SENDER_ID - 123456789012 - PLIST_VERSION - 1 - BUNDLE_ID - com.flutter.starter.template.flutterStarterTemplate - PROJECT_ID - fake-project-id - STORAGE_BUCKET - fake-project-id.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:123456789012:ios:321abc456def7890 - - - EOF - + echo 'API_KEYfakeGCM_SENDER_ID123PLIST_VERSION1BUNDLE_IDcom.flutter.starter.template.flutterStarterTemplatePROJECT_IDfakeSTORAGE_BUCKETfakeIS_ADS_ENABLEDIS_ANALYTICS_ENABLEDIS_APPINVITE_ENABLEDIS_GCM_ENABLEDIS_SIGNIN_ENABLEDGOOGLE_APP_ID1:123:ios:123' > ios/Runner/GoogleService-Info.plist + - name: Get dependencies run: flutter pub get - - name: Install iOS dependencies + - name: Install Pods run: | cd ios - rm -rf Pods - rm -rf Podfile.lock - # Устанавливаем заново + rm -rf Pods Podfile.lock pod install --repo-update - cd .. - - name: Build iOS app (no codesign) + - name: Build iOS app run: | - flutter build ios --release --no-codesign --verbose \ - --build-name=1.0.0 \ - --build-number=${{ github.run_number }} + flutter build ios --release --no-codesign --build-number=${{ github.run_number }} - - name: Create unsigned IPA + - name: Create IPA run: | mkdir -p Payload cp -R build/ios/iphoneos/Runner.app Payload/ - IPA_NAME="flutter-starter-ios-${{ github.run_number }}-unsigned.ipa" zip -r "$IPA_NAME" Payload - rm -rf Payload - - echo "IPA created: $IPA_NAME" - ls -lh *.ipa - name: Upload iOS IPA artifact uses: actions/upload-artifact@v4 @@ -436,29 +202,14 @@ jobs: path: "*.ipa" retention-days: 30 compression-level: 0 - if-no-files-found: error - - - name: Generate iOS build summary + - name: Generate Summary run: | echo "## 🍎 iOS IPA Build Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Build Info:**" >> $GITHUB_STEP_SUMMARY - echo "- Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Branch: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Flutter: \`${{ env.FLUTTER_VERSION }}\`" >> $GITHUB_STEP_SUMMARY - echo "- Build Number: \`${{ github.run_number }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY echo "| File | Size |" >> $GITHUB_STEP_SUMMARY echo "|------|------|" >> $GITHUB_STEP_SUMMARY - for ipa in *.ipa; do - if [ -f "$ipa" ]; then - size=$(du -h "$ipa" | cut -f1) - echo "| \`$ipa\` | $size |" >> $GITHUB_STEP_SUMMARY - fi + size=$(du -h "$ipa" | cut -f1) + echo "| \`$ipa\` | $size |" >> $GITHUB_STEP_SUMMARY done - - echo "" >> $GITHUB_STEP_SUMMARY - echo "⚠️ **Note**: This is an unsigned IPA for testing purposes only." >> $GITHUB_STEP_SUMMARY - echo "For App Store distribution, code signing is required." >> $GITHUB_STEP_SUMMARY + echo "⚠️ Unsigned IPA for testing only." >> $GITHUB_STEP_SUMMARY From 6f9ff7840e7d038519cd03951972b491e0a82597 Mon Sep 17 00:00:00 2001 From: CowboyGH Date: Fri, 12 Dec 2025 18:50:57 +0700 Subject: [PATCH 13/13] fix: add step to format generated code in CI pipeline --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 223578d..34a1348 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,6 +56,9 @@ jobs: - name: Run build_runner run: dart run build_runner build --delete-conflicting-outputs + - name: Format generated code + run: dart format . + - name: Verify formatting run: dart format --set-exit-if-changed lib/ test/ continue-on-error: false