ci(codemagic): add staging + production workflows for legacy store builds
Codemagic se usa como herramienta de release (Play Store + App Store), no como CI general. La validación de develop / feature branches sigue siendo local. Workflows: - staging_android / staging_ios — push a fusion-app → Play Internal Track + TestFlight - production_android / production_ios — tag 1.0.0(N) → Play (draft) + App Store (manual review) Todos los workflows publicables buildean en APP_MODE=legacy mientras el roadmap premium de Treezor no esté listo para producción. Scripts en codemagic_scripts/: - project_setup.sh — melos bootstrap (build_runner no es necesario, los .freezed.dart y .g.dart están commiteados) - analyze_and_test.sh — melos run analyze + test con --no-select obligatorio (sin TTY melos crashea con StdinException) - android_dependencies.sh — verifica gradle wrapper - android_key_properties.sh — escribe key.properties desde CM_KEYSTORE_* - ios_signing_cocoapods.sh — gem install cocoapods + xcode-project use-profiles - flutter_build.sh — build aab/ipa con --dart-define=APP_MODE; parser para tags formato 1.0.0(N) o v1.0.0(N) en producción - print_versions.sh — log de versiones y herramientas - build_status.sh — exit + notificación Google Chat ante fallo Setup detallado y bloqueantes externos en docs/codemagic-integration.md.
This commit is contained in:
167
codemagic.yaml
Normal file
167
codemagic.yaml
Normal file
@@ -0,0 +1,167 @@
|
||||
workflows:
|
||||
staging_android:
|
||||
name: Staging Android
|
||||
instance_type: linux_x2
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
android_signing:
|
||||
- upload_keystore
|
||||
groups:
|
||||
- codemagic-staging
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events:
|
||||
- push
|
||||
branch_patterns:
|
||||
- pattern: fusion-app
|
||||
include: true
|
||||
scripts:
|
||||
- name: Setup project (melos bootstrap)
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh android staging
|
||||
- name: Android dependencies
|
||||
script: sh ./codemagic_scripts/android_dependencies.sh
|
||||
- name: Set up key.properties
|
||||
script: sh ./codemagic_scripts/android_key_properties.sh
|
||||
- name: Build Android app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh android staging legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/app/outputs/bundle/stagingRelease/app-staging-release.aab
|
||||
publishing:
|
||||
google_play:
|
||||
track: internal
|
||||
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
|
||||
submit_as_draft: false
|
||||
scripts:
|
||||
- name: Report status
|
||||
script: sh ./codemagic_scripts/build_status.sh android staging
|
||||
|
||||
staging_ios:
|
||||
name: Staging iOS
|
||||
instance_type: mac_mini_m2
|
||||
integrations:
|
||||
app_store_connect: SaveFamily ASC Key
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
xcode: latest
|
||||
ios_signing:
|
||||
distribution_type: app_store
|
||||
bundle_identifier: com.savefamily.app.stag
|
||||
groups:
|
||||
- codemagic-staging
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events:
|
||||
- push
|
||||
branch_patterns:
|
||||
- pattern: fusion-app
|
||||
include: true
|
||||
scripts:
|
||||
- name: Setup project (melos bootstrap)
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: iOS signing + CocoaPods
|
||||
script: sh ./codemagic_scripts/ios_signing_cocoapods.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh ios staging
|
||||
- name: Build iOS app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh ios staging legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/ios/ipa/*.ipa
|
||||
publishing:
|
||||
app_store_connect:
|
||||
auth: integration
|
||||
submit_to_testflight: true
|
||||
scripts:
|
||||
- name: Report status
|
||||
script: sh ./codemagic_scripts/build_status.sh ios staging
|
||||
|
||||
production_android:
|
||||
name: Production Android
|
||||
instance_type: linux_x2
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
android_signing:
|
||||
- upload_keystore
|
||||
groups:
|
||||
- codemagic-production
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events:
|
||||
- tag
|
||||
scripts:
|
||||
- name: Setup project (melos bootstrap)
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh android production
|
||||
- name: Android dependencies
|
||||
script: sh ./codemagic_scripts/android_dependencies.sh
|
||||
- name: Set up key.properties
|
||||
script: sh ./codemagic_scripts/android_key_properties.sh
|
||||
- name: Build Android app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh android production legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/app/outputs/bundle/productionRelease/app-production-release.aab
|
||||
publishing:
|
||||
google_play:
|
||||
track: internal
|
||||
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
|
||||
submit_as_draft: true
|
||||
scripts:
|
||||
- name: Report status
|
||||
script: sh ./codemagic_scripts/build_status.sh android production
|
||||
|
||||
production_ios:
|
||||
name: Production iOS
|
||||
instance_type: mac_mini_m2
|
||||
integrations:
|
||||
app_store_connect: SaveFamily ASC Key
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
xcode: latest
|
||||
ios_signing:
|
||||
distribution_type: app_store
|
||||
bundle_identifier: com.savefamily.app
|
||||
groups:
|
||||
- codemagic-production
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events:
|
||||
- tag
|
||||
scripts:
|
||||
- name: Setup project (melos bootstrap)
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: iOS signing + CocoaPods
|
||||
script: sh ./codemagic_scripts/ios_signing_cocoapods.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh ios production
|
||||
- name: Build iOS app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh ios production legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/ios/ipa/*.ipa
|
||||
publishing:
|
||||
app_store_connect:
|
||||
auth: integration
|
||||
submit_to_app_store: true
|
||||
release_type: MANUAL
|
||||
cancel_previous_submissions: true
|
||||
copyright: 2026 SaveFamily
|
||||
scripts:
|
||||
- name: Report status
|
||||
script: sh ./codemagic_scripts/build_status.sh ios production
|
||||
5
codemagic_scripts/analyze_and_test.sh
Executable file
5
codemagic_scripts/analyze_and_test.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
melos run analyze --no-select
|
||||
melos run test --no-select
|
||||
5
codemagic_scripts/android_dependencies.sh
Executable file
5
codemagic_scripts/android_dependencies.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
cd apps/mobile_app/android
|
||||
./gradlew --version
|
||||
9
codemagic_scripts/android_key_properties.sh
Executable file
9
codemagic_scripts/android_key_properties.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
cat > "$CM_BUILD_DIR/apps/mobile_app/android/key.properties" <<EOF
|
||||
storePassword=$CM_KEYSTORE_PASSWORD
|
||||
keyPassword=$CM_KEY_PASSWORD
|
||||
keyAlias=$CM_KEY_ALIAS
|
||||
storeFile=$CM_KEYSTORE_PATH
|
||||
EOF
|
||||
22
codemagic_scripts/build_status.sh
Executable file
22
codemagic_scripts/build_status.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
APP_PLATFORM=$1
|
||||
ENVIRONMENT=$2
|
||||
|
||||
if [ -f ~/SUCCESS ]; then
|
||||
echo "Build finished successfully ($APP_PLATFORM / $ENVIRONMENT)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Codemagic pipeline failed in $ENVIRONMENT workflow ($APP_PLATFORM)"
|
||||
|
||||
if [ -n "${GOOGLE_BUILDS_CHAT_URL:-}" ]; then
|
||||
curl -fsS -X POST -H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"text": "*Codemagic* failed in '"$ENVIRONMENT"' / '"$APP_PLATFORM"' for commit '"$CM_COMMIT"' (build '"$BUILD_NUMBER"').\nBuild: https://codemagic.io/app/'"$CM_PROJECT_ID"'/build/'"$CM_BUILD_ID"'"
|
||||
}' \
|
||||
"$GOOGLE_BUILDS_CHAT_URL" || echo "(google chat webhook post failed)"
|
||||
fi
|
||||
|
||||
exit 1
|
||||
52
codemagic_scripts/flutter_build.sh
Executable file
52
codemagic_scripts/flutter_build.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
APP_PLATFORM=$1
|
||||
ENVIRONMENT=$2
|
||||
APP_MODE=${3:-legacy}
|
||||
|
||||
APP_DIR="apps/mobile_app"
|
||||
PUBSPEC="$APP_DIR/pubspec.yaml"
|
||||
|
||||
case "$ENVIRONMENT" in
|
||||
"staging")
|
||||
PUBSPEC_VERSION=$(grep '^version:' "$PUBSPEC" | sed -E 's/^version:[[:space:]]+//')
|
||||
VERSION_NUMBER=${PUBSPEC_VERSION%+*}
|
||||
ENV_BUILD_NUMBER=${PUBSPEC_VERSION#*+}
|
||||
;;
|
||||
"production")
|
||||
VERSION_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?([0-9]+(\.[0-9]+)+)\([0-9]+\)$/\1/')
|
||||
ENV_BUILD_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?[0-9]+(\.[0-9]+)+\(([0-9]+)\)$/\2/')
|
||||
if [ -z "$VERSION_NUMBER" ] || [ -z "$ENV_BUILD_NUMBER" ]; then
|
||||
echo "Tag inválido (esperado 1.0.0(N) o v1.0.0(N)): $CM_TAG"; exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "ENVIRONMENT inválido: $ENVIRONMENT (esperado staging o production)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
COMMON_FLAGS=(
|
||||
--release
|
||||
--flavor "$ENVIRONMENT"
|
||||
-t "lib/main_$ENVIRONMENT.dart"
|
||||
--build-name="$VERSION_NUMBER"
|
||||
--build-number="$ENV_BUILD_NUMBER"
|
||||
--dart-define-from-file="config/$ENVIRONMENT.json"
|
||||
--dart-define=APP_MODE="$APP_MODE"
|
||||
)
|
||||
|
||||
case "$APP_PLATFORM" in
|
||||
"android")
|
||||
flutter build appbundle "${COMMON_FLAGS[@]}"
|
||||
;;
|
||||
"ios")
|
||||
flutter build ipa \
|
||||
"${COMMON_FLAGS[@]}" \
|
||||
--export-options-plist=/Users/builder/export_options.plist
|
||||
;;
|
||||
*) echo "APP_PLATFORM inválido: $APP_PLATFORM (esperado android o ios)"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "Build OK: $APP_PLATFORM / $ENVIRONMENT / mode=$APP_MODE (v$VERSION_NUMBER+$ENV_BUILD_NUMBER)"
|
||||
5
codemagic_scripts/ios_signing_cocoapods.sh
Executable file
5
codemagic_scripts/ios_signing_cocoapods.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
gem install cocoapods -v "$COCOAPODS_VERSION"
|
||||
xcode-project use-profiles --project apps/mobile_app/ios/Runner.xcodeproj
|
||||
33
codemagic_scripts/print_versions.sh
Executable file
33
codemagic_scripts/print_versions.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
APP_PLATFORM=$1
|
||||
ENVIRONMENT=$2
|
||||
|
||||
PUBSPEC="apps/mobile_app/pubspec.yaml"
|
||||
|
||||
case "$ENVIRONMENT" in
|
||||
"staging")
|
||||
PUBSPEC_VERSION=$(grep '^version:' "$PUBSPEC" | sed -E 's/^version:[[:space:]]+//')
|
||||
VERSION_NUMBER=${PUBSPEC_VERSION%+*}
|
||||
ENV_BUILD_NUMBER=${PUBSPEC_VERSION#*+}
|
||||
;;
|
||||
"production")
|
||||
VERSION_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?([0-9]+(\.[0-9]+)+)\([0-9]+\)$/\1/')
|
||||
ENV_BUILD_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?[0-9]+(\.[0-9]+)+\(([0-9]+)\)$/\2/')
|
||||
;;
|
||||
*)
|
||||
echo "ENVIRONMENT inválido: $ENVIRONMENT"; exit 1 ;;
|
||||
esac
|
||||
|
||||
printf "\033[32m%s\033[0m %s\n" "Flutter version:" "${FLUTTER_VERSION:-unknown}"
|
||||
printf "\033[32m%s\033[0m %s\n" "App version:" "$VERSION_NUMBER"
|
||||
printf "\033[32m%s\033[0m %s\n" "Build number:" "$ENV_BUILD_NUMBER"
|
||||
printf "\033[32m%s\033[0m %s\n" "Environment:" "$ENVIRONMENT"
|
||||
|
||||
if [ "$APP_PLATFORM" = "ios" ]; then
|
||||
printf "\033[32m%s\033[0m\n" "CocoaPods version:"
|
||||
pod --version
|
||||
printf "\033[32m%s\033[0m\n" "Xcode version:"
|
||||
xcodebuild -version
|
||||
fi
|
||||
5
codemagic_scripts/project_setup.sh
Executable file
5
codemagic_scripts/project_setup.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
dart pub global activate melos 7.5.1
|
||||
melos bootstrap
|
||||
469
docs/codemagic-integration.md
Normal file
469
docs/codemagic-integration.md
Normal file
@@ -0,0 +1,469 @@
|
||||
# Codemagic integration plan
|
||||
|
||||
Guía para integrar Codemagic en `sf-app-platform`. **Codemagic se usa exclusivamente como herramienta de release a tiendas** (Play Store + App Store), no como CI general. La validación de develop / feature branches sigue siendo local (`melos run analyze`, `melos run test`, `flutter build` manual).
|
||||
|
||||
El plan está adaptado a la realidad de este repo (monorepo Melos + pub workspace, configs gitignored, generated files commiteados, fusión legacy↔payment en curso).
|
||||
|
||||
## Decisiones tomadas
|
||||
|
||||
| Tema | Decisión | Por qué |
|
||||
| --- | --- | --- |
|
||||
| Alcance | **Solo 4 workflows**: staging + production × Android + iOS | Codemagic = herramienta de release. Development no se corre en CI — la validación es local |
|
||||
| Triggers | `fusion-app` → staging, tag → prod | La rama `main` no está activa; `fusion-app` es el integration branch durante la fusión legacy↔payment |
|
||||
| Formato de tag prod | `v1.0.0(N)` / `1.0.0(N)` (formato actual) | Mantener convención existente — última release fue `v1.0.0(8)` |
|
||||
| Configs JSON | **Codemagic Encrypted Files** (solo staging + production) | Más seguro y auditable que env vars; el repo los tiene gitignored y Codemagic los materializa antes del build |
|
||||
| Code generation | **No correr `build_runner` en CI** | 355 `.freezed.dart`/`.g.dart` están commiteados — solo `melos bootstrap` |
|
||||
| Quality gates | `melos run analyze --no-select` + `melos run test --no-select` bloqueantes | Si alguien pushea código roto a `fusion-app`, no queremos que llegue a Play Internal Track ni TestFlight. **`--no-select` es obligatorio** (sin TTY melos crashea) |
|
||||
| Versionado | `version` desde `pubspec.yaml` (manual), build number desde tag en prod | Como hoy. CI no toca pubspec.yaml |
|
||||
| **APP_MODE en builds a tiendas** | **Siempre `legacy`** | Aunque la rama `fusion-app` integra legacy+payment, los AAB/IPA publicados a App Store y Play Store deben arrancar en flujo legacy hasta que el roadmap premium de Treezor esté listo para producción |
|
||||
| Web | No incluido | sf-app-platform es mobile-only |
|
||||
|
||||
## Cosas del repo que importan (chequeadas, no asumidas)
|
||||
|
||||
- **Entry points**: `apps/mobile_app/lib/main_{development,staging,production}.dart`
|
||||
- **Configs (gitignored)**: `apps/mobile_app/config/{development,staging,production}.json` — claves: `env`, `apiBaseUrl`, `apiOrigin`, `wsUrl`, `juphoonAppKey`
|
||||
- **iOS schemes por flavor**: `development.xcscheme`, `staging.xcscheme`, `production.xcscheme` (ya existen)
|
||||
- **iOS Firebase configs por flavor**: `apps/mobile_app/ios/flavors/{dev,stag,prod}/GoogleService-Info.plist` (ya existen — el script Build Phase los copia)
|
||||
- **Android Firebase configs**: ya están configuradas. `apps/mobile_app/android/app/google-services.json` (root) contiene los packages `com.savefamily.app.dev` Y `com.savefamily.app.stag` (project `sf-platform-pre`); `src/production/google-services.json` override para `com.savefamily.app` (project `sf-platform-pro`). Commits de referencia: `81284d7e`, `e83adbfd`
|
||||
- **Bundle IDs**:
|
||||
- iOS / Android: `com.savefamily.app.dev`, `com.savefamily.app.stag`, `com.savefamily.app`
|
||||
- **Bridges nativos**: `flutter_treezor_entrust_sdk_bridge`, `sca_treezor` son `path:` dependencies locales. No requieren maven privado ni Pod source extra
|
||||
- **Native repos**: solo `google()` + `mavenCentral()`. Cero credenciales adicionales en Gradle
|
||||
- **AntelopAppDelegate caveat**: el swizzling de FCM se rompe si se regenera `Runner.xcodeproj`. CI no debe regenerar el proyecto Xcode
|
||||
- **iOS deployment target**: 15.0 mínimo
|
||||
- **Dart SDK**: `^3.9.2` → Flutter ≥ 3.27 (requerido por `resolution: workspace` en pubspec raíz)
|
||||
- **App Store Connect / Apple Developer**: Team `KQ73NP6L4Q`, Account Holder Jorge Alvarez — la API key para Codemagic la tiene que generar él
|
||||
- **App Store IDs**: staging `6759873957`, prod `6759875039`
|
||||
- **Play Internal track**: prod Android track id `4700720707717247246`
|
||||
|
||||
## Estrategia de configs vía Encrypted Files
|
||||
|
||||
En Codemagic UI → **Environment variables → Add encrypted file**. Subir cada JSON con `Path on virtual machine` = path relativo al build dir:
|
||||
|
||||
| Archivo local | Path en VM |
|
||||
| --- | --- |
|
||||
| `apps/mobile_app/config/staging.json` | `apps/mobile_app/config/staging.json` |
|
||||
| `apps/mobile_app/config/production.json` | `apps/mobile_app/config/production.json` |
|
||||
|
||||
Codemagic los desencripta en disco antes de los scripts. **No hace falta script de materialización**. Si una key rota (ej. `juphoonAppKey`), se re-sube el archivo desde la UI sin tocar el repo ni env vars.
|
||||
|
||||
## Archivos a agregar al repo
|
||||
|
||||
```
|
||||
sf-app-platform/
|
||||
├── codemagic.yaml
|
||||
└── codemagic_scripts/
|
||||
├── project_setup.sh # melos bootstrap (NO build_runner)
|
||||
├── android_dependencies.sh # verifica gradle wrapper, flutter doctor
|
||||
├── android_key_properties.sh # escribe key.properties desde CM_KEYSTORE_*
|
||||
├── ios_signing_cocoapods.sh # gem install cocoapods, xcode-project use-profiles
|
||||
├── flutter_build.sh # build aab / ipa con parser para 1.0.0(N)
|
||||
├── print_versions.sh # log de versiones
|
||||
├── analyze_and_test.sh # melos run analyze && melos run test
|
||||
└── build_status.sh # report final + Google Chat fallo
|
||||
```
|
||||
|
||||
Permisos: `chmod +x codemagic_scripts/*.sh`.
|
||||
|
||||
## Scripts
|
||||
|
||||
### `project_setup.sh`
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
dart pub global activate melos 7.5.1
|
||||
melos bootstrap
|
||||
```
|
||||
|
||||
Nada de `build_runner`: los `.freezed.dart` y `.g.dart` ya están commiteados (355 archivos). Si en el futuro decidimos dejar de commitearlos, se agrega `melos run generate` acá.
|
||||
|
||||
### `analyze_and_test.sh`
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
melos run analyze --no-select
|
||||
melos run test --no-select
|
||||
```
|
||||
|
||||
Los dos targets ya están definidos en el bloque `melos.scripts` del `pubspec.yaml` raíz.
|
||||
|
||||
**Críticos** (aprendidos en Fase 0):
|
||||
- `--no-select` es obligatorio en non-TTY. Sin esto, melos prompts interactivamente y crashea con `StdinException`
|
||||
- `set -o pipefail` evita que pipes downstream enmascaren un exit code de fallo
|
||||
|
||||
### `android_dependencies.sh`
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
cd apps/mobile_app/android
|
||||
./gradlew --version
|
||||
```
|
||||
|
||||
Diferencia con effi: no se regenera el wrapper (`gradle wrapper --gradle-version 7.4`). El repo ya commitea el wrapper actual; regenerarlo en cada build es ruido.
|
||||
|
||||
### `android_key_properties.sh`
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
cat > "$CM_BUILD_DIR/apps/mobile_app/android/key.properties" <<EOF
|
||||
storePassword=$CM_KEYSTORE_PASSWORD
|
||||
keyPassword=$CM_KEY_PASSWORD
|
||||
keyAlias=$CM_KEY_ALIAS
|
||||
storeFile=$CM_KEYSTORE_PATH
|
||||
EOF
|
||||
```
|
||||
|
||||
El `build.gradle.kts` busca `rootProject.file("key.properties")` → `apps/mobile_app/android/key.properties`.
|
||||
|
||||
### `ios_signing_cocoapods.sh`
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
gem install cocoapods -v $COCOAPODS_VERSION
|
||||
xcode-project use-profiles --project apps/mobile_app/ios/Runner.xcodeproj
|
||||
```
|
||||
|
||||
`xcode-project` viene preinstalado en imágenes Mac mini de Codemagic.
|
||||
|
||||
### `flutter_build.sh`
|
||||
|
||||
Parser adaptado al formato real `1.0.0(N)` y `pubspec.yaml: version: 1.0.0+10`. `APP_MODE` por flavor: staging y production fuerzan `legacy` (lo que se publica a tiendas); development queda en el default del código (`legacy`, pero se puede sobreescribir vía un 3er argumento si en el futuro queremos un smoke test del flujo payment).
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
APP_PLATFORM=$1 # "android" o "ios"
|
||||
ENVIRONMENT=$2 # "development", "staging" o "production"
|
||||
APP_MODE=${3:-legacy} # "legacy" o "payment". Default legacy (publicable a tiendas)
|
||||
|
||||
APP_DIR="apps/mobile_app"
|
||||
PUBSPEC="$APP_DIR/pubspec.yaml"
|
||||
|
||||
case "$ENVIRONMENT" in
|
||||
"development"|"staging")
|
||||
PUBSPEC_VERSION=$(grep '^version:' "$PUBSPEC" | sed -E 's/^version:[[:space:]]+//')
|
||||
VERSION_NUMBER=${PUBSPEC_VERSION%+*}
|
||||
ENV_BUILD_NUMBER=${PUBSPEC_VERSION#*+}
|
||||
;;
|
||||
"production")
|
||||
VERSION_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?([0-9]+(\.[0-9]+)+)\([0-9]+\)$/\1/')
|
||||
ENV_BUILD_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?[0-9]+(\.[0-9]+)+\(([0-9]+)\)$/\2/')
|
||||
if [ -z "$VERSION_NUMBER" ] || [ -z "$ENV_BUILD_NUMBER" ]; then
|
||||
echo "Tag inválido (esperado 1.0.0(N) o v1.0.0(N)): $CM_TAG"; exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "ENVIRONMENT inválido: $ENVIRONMENT"; exit 1 ;;
|
||||
esac
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
COMMON_FLAGS=(
|
||||
--release
|
||||
--flavor "$ENVIRONMENT"
|
||||
-t "lib/main_$ENVIRONMENT.dart"
|
||||
--build-name="$VERSION_NUMBER"
|
||||
--build-number="$ENV_BUILD_NUMBER"
|
||||
--dart-define-from-file="config/$ENVIRONMENT.json"
|
||||
--dart-define=APP_MODE="$APP_MODE"
|
||||
)
|
||||
|
||||
case "$APP_PLATFORM" in
|
||||
"android")
|
||||
flutter build appbundle "${COMMON_FLAGS[@]}"
|
||||
;;
|
||||
"ios")
|
||||
flutter build ipa \
|
||||
"${COMMON_FLAGS[@]}" \
|
||||
--export-options-plist=/Users/builder/export_options.plist
|
||||
;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "Build OK: $APP_PLATFORM / $ENVIRONMENT / mode=$APP_MODE (v$VERSION_NUMBER+$ENV_BUILD_NUMBER)"
|
||||
```
|
||||
|
||||
### `print_versions.sh`
|
||||
|
||||
Mismo concepto que effi, ajustado al parser:
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
APP_PLATFORM=$1
|
||||
ENVIRONMENT=$2
|
||||
|
||||
case "$ENVIRONMENT" in
|
||||
"development"|"staging")
|
||||
PUBSPEC_VERSION=$(grep '^version:' apps/mobile_app/pubspec.yaml | sed -E 's/^version:[[:space:]]+//')
|
||||
VERSION_NUMBER=${PUBSPEC_VERSION%+*}
|
||||
ENV_BUILD_NUMBER=${PUBSPEC_VERSION#*+}
|
||||
;;
|
||||
"production")
|
||||
VERSION_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?([0-9]+(\.[0-9]+)+)\([0-9]+\)$/\1/')
|
||||
ENV_BUILD_NUMBER=$(echo "$CM_TAG" | sed -E 's/^v?[0-9]+(\.[0-9]+)+\(([0-9]+)\)$/\2/')
|
||||
;;
|
||||
esac
|
||||
|
||||
printf "\033[32m%s\033[0m %s\n" "Flutter version:" "$FLUTTER_VERSION"
|
||||
printf "\033[32m%s\033[0m %s\n" "App version:" "$VERSION_NUMBER"
|
||||
printf "\033[32m%s\033[0m %s\n" "Build number:" "$ENV_BUILD_NUMBER"
|
||||
|
||||
if [ "$APP_PLATFORM" = "ios" ]; then
|
||||
pod --version
|
||||
xcodebuild -version
|
||||
fi
|
||||
```
|
||||
|
||||
### `build_status.sh`
|
||||
|
||||
Idéntico a effi (notificación Google Chat ante fallo).
|
||||
|
||||
## `codemagic.yaml`
|
||||
|
||||
Cuatro workflows, organizados por (flavor × plataforma). Sin workflows de development — la validación de develop branch se hace local.
|
||||
|
||||
```yaml
|
||||
workflows:
|
||||
# Staging: build legacy publicable a Play Internal Track / TestFlight desde fusion-app
|
||||
staging_android:
|
||||
name: Staging Android
|
||||
instance_type: linux_x2
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
android_signing: [upload_keystore]
|
||||
groups:
|
||||
- codemagic-staging
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events: [push]
|
||||
branch_patterns:
|
||||
- pattern: fusion-app
|
||||
include: true
|
||||
scripts:
|
||||
- name: Setup project
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh android staging
|
||||
- name: Android dependencies
|
||||
script: sh ./codemagic_scripts/android_dependencies.sh
|
||||
- name: Set up key.properties
|
||||
script: sh ./codemagic_scripts/android_key_properties.sh
|
||||
- name: Build Android app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh android staging legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/app/outputs/bundle/stagingRelease/app-staging-release.aab
|
||||
publishing:
|
||||
google_play:
|
||||
track: internal
|
||||
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
|
||||
submit_as_draft: false
|
||||
scripts:
|
||||
- name: Report status
|
||||
script: sh ./codemagic_scripts/build_status.sh android staging
|
||||
|
||||
staging_ios:
|
||||
name: Staging iOS
|
||||
instance_type: mac_mini_m2
|
||||
integrations:
|
||||
app_store_connect: SaveFamily ASC Key # crear en CM con la key generada por Jorge Alvarez
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
xcode: latest
|
||||
ios_signing:
|
||||
distribution_type: app_store
|
||||
bundle_identifier: com.savefamily.app.stag
|
||||
groups:
|
||||
- codemagic-staging
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events: [push]
|
||||
branch_patterns:
|
||||
- pattern: fusion-app
|
||||
include: true
|
||||
scripts:
|
||||
- name: Setup project
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: iOS signing + CocoaPods
|
||||
script: sh ./codemagic_scripts/ios_signing_cocoapods.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh ios staging
|
||||
- name: Build iOS app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh ios staging legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/ios/ipa/*.ipa
|
||||
publishing:
|
||||
app_store_connect:
|
||||
auth: integration
|
||||
submit_to_testflight: true
|
||||
scripts:
|
||||
- name: Report status
|
||||
script: sh ./codemagic_scripts/build_status.sh ios staging
|
||||
|
||||
# Production: build legacy disparado por tag 1.0.0(N) o v1.0.0(N)
|
||||
production_android:
|
||||
name: Production Android
|
||||
instance_type: linux_x2
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
android_signing: [upload_keystore]
|
||||
groups:
|
||||
- codemagic-production
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events: [tag]
|
||||
scripts:
|
||||
- name: Setup project
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh android production
|
||||
- name: Android dependencies
|
||||
script: sh ./codemagic_scripts/android_dependencies.sh
|
||||
- name: Set up key.properties
|
||||
script: sh ./codemagic_scripts/android_key_properties.sh
|
||||
- name: Build Android app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh android production legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/app/outputs/bundle/productionRelease/app-production-release.aab
|
||||
publishing:
|
||||
google_play:
|
||||
track: internal
|
||||
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
|
||||
submit_as_draft: true # producción se manda como draft para revisión humana
|
||||
|
||||
production_ios:
|
||||
name: Production iOS
|
||||
instance_type: mac_mini_m2
|
||||
integrations:
|
||||
app_store_connect: SaveFamily ASC Key
|
||||
environment:
|
||||
flutter: $FLUTTER_VERSION
|
||||
xcode: latest
|
||||
ios_signing:
|
||||
distribution_type: app_store
|
||||
bundle_identifier: com.savefamily.app
|
||||
groups:
|
||||
- codemagic-production
|
||||
- codemagic-common-vars
|
||||
triggering:
|
||||
events: [tag]
|
||||
scripts:
|
||||
- name: Setup project
|
||||
script: sh ./codemagic_scripts/project_setup.sh
|
||||
- name: Analyze + test (bloqueante)
|
||||
script: sh ./codemagic_scripts/analyze_and_test.sh
|
||||
- name: iOS signing + CocoaPods
|
||||
script: sh ./codemagic_scripts/ios_signing_cocoapods.sh
|
||||
- name: Print versions
|
||||
script: sh ./codemagic_scripts/print_versions.sh ios production
|
||||
- name: Build iOS app (APP_MODE=legacy)
|
||||
script: sh ./codemagic_scripts/flutter_build.sh ios production legacy
|
||||
- name: Mark success
|
||||
script: touch ~/SUCCESS
|
||||
artifacts:
|
||||
- apps/mobile_app/build/ios/ipa/*.ipa
|
||||
publishing:
|
||||
app_store_connect:
|
||||
auth: integration
|
||||
submit_to_app_store: true
|
||||
release_type: MANUAL
|
||||
cancel_previous_submissions: true
|
||||
copyright: 2026 SaveFamily
|
||||
```
|
||||
|
||||
## Environment groups en Codemagic
|
||||
|
||||
### `codemagic-common-vars`
|
||||
- `FLUTTER_VERSION` = `3.27.0` (mínimo para `resolution: workspace`)
|
||||
- `COCOAPODS_VERSION` = `1.15.2`
|
||||
- `GOOGLE_BUILDS_CHAT_URL` (opcional, webhook fallos)
|
||||
- `GOOGLE_CHAT_URL` (opcional, webhook releases)
|
||||
|
||||
### `codemagic-staging`
|
||||
- `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`
|
||||
|
||||
### `codemagic-production`
|
||||
- `GCLOUD_SERVICE_ACCOUNT_CREDENTIALS`
|
||||
- `RELEASE_NOTES_LANGUAGE` = `es-ES` (o lo que defina el equipo)
|
||||
- `RELEASE_NOTES` = texto multilinea de release notes
|
||||
|
||||
### Code Signing (UI separada de groups)
|
||||
- **Android keystore** → uploaded como `upload_keystore`. Codemagic expone `CM_KEYSTORE_PATH`, `CM_KEYSTORE_PASSWORD`, `CM_KEY_PASSWORD`, `CM_KEY_ALIAS`
|
||||
- **App Store Connect API integration** → la genera Jorge Alvarez desde App Store Connect → Users and Access → Keys. En CM se llama `SaveFamily ASC Key` (referenciado desde `integrations.app_store_connect`)
|
||||
|
||||
### Encrypted Files (UI separada también)
|
||||
- `apps/mobile_app/config/staging.json`
|
||||
- `apps/mobile_app/config/production.json`
|
||||
|
||||
## Bloqueantes antes de prender CI
|
||||
|
||||
Cosas que hay que resolver antes de que el primer build dev verde sea posible:
|
||||
|
||||
1. **App Store Connect API key** — Jorge Alvarez (Account Holder) tiene que generar la key desde App Store Connect → Users and Access → Integrations → Team Keys, descargar el `.p8` (solo se puede descargar una vez), y configurar la integration en Codemagic
|
||||
2. **Provisioning profiles**:
|
||||
- `com.savefamily.app.stag` → tipo **app_store** (para TestFlight)
|
||||
- `com.savefamily.app` → tipo **app_store** (para release)
|
||||
- El perfil `com.savefamily.app.dev` no se usa en CI (solo desarrollo local en Xcode)
|
||||
3. **Upload keystore Android** — verificar si ya existe el upload key actual del equipo. Si no, generar uno (`keytool -genkey -v -keystore upload.keystore ...`) y registrarlo en Play Console Internal App Sharing
|
||||
4. **Google Play service account** — necesita `androidpublisher` API enabled + permisos en Play Console (Release Manager) para `com.savefamily.app.stag` (staging) y `com.savefamily.app` (production)
|
||||
|
||||
## Pasos para arrancar (orden)
|
||||
|
||||
1. **Resolver bloqueantes** (sección anterior)
|
||||
2. **Crear archivos** en el repo:
|
||||
- `codemagic.yaml`
|
||||
- `codemagic_scripts/*.sh` (8 scripts) con `chmod +x`
|
||||
- Commitear en feature branch + PR a `fusion-app`
|
||||
3. **Conectar repo a Codemagic UI** (`SaveFamily/sf-app-platform`)
|
||||
4. **Crear los 3 Environment Groups** (common-vars, staging, production) y poblar variables
|
||||
5. **Subir Encrypted Files** (2 JSON: staging + production)
|
||||
6. **Subir keystore Android** + configurar App Store Connect integration
|
||||
7. **Disparar `staging_android` MANUAL** desde la UI (sin esperar push) para validar `melos bootstrap` + analyze + test + build end-to-end. Validar el AAB en Play Internal Track con un tester
|
||||
8. **Disparar `staging_ios` MANUAL** — más puntos de falla por signing/Pods/CocoaPods, esperar fricción. Validar el IPA en TestFlight
|
||||
9. **Activar trigger automático** en push a `fusion-app` solo cuando ambos staging estén verdes
|
||||
10. **Tag de RC para producción** (ej. `0.99.0(1)`) para validar el pipeline de tag-driven release sin afectar producción real
|
||||
11. **Primer release real con tag** `1.0.0(N)` solo después de coordinación con stakeholders y validación del RC
|
||||
|
||||
## Riesgos y caveats específicos
|
||||
|
||||
- **Tiempo de build estimado**: `melos bootstrap` (~1 min) + `melos run analyze` (~2-3 min, 30+ paquetes) + `melos run test` (depende, ~2-5 min) + build (~5-10 min). Total: 10-20 min por workflow. Mac mini m2 escala mejor que linux_x2 para iOS
|
||||
- **AntelopAppDelegate**: cualquier `pod install` con `--repo-update` puede regenerar archivos que rompen el swizzling. El `Podfile.lock` ya está commiteado — CI debe usar `pod install` plain, no `pod update`
|
||||
- **`resolution: workspace`** requiere Flutter ≥ 3.27. Pinear `FLUTTER_VERSION` exacto en `codemagic-common-vars`, no `latest`
|
||||
- **`flutter_secure_storage`** está pinned a `9.2.4` vía `dependency_overrides`. CI no debería romper esto pero si melos hace algo raro con overrides en workspace, validar localmente primero
|
||||
- **Tests con backend mock**: hay tests que llaman APIs (`questia_api_test`?). Si fallan en CI sin red, hay que skiparlos con `@Skip()` o un tag de melos. Validar antes del primer push
|
||||
- **Flag `--no-select` obligatorio en `melos run`**: en non-TTY (cualquier CI) melos prompts y crashea con `StdinException`. Validado en Fase 0
|
||||
- **`set -o pipefail` en todos los scripts**: pipes como `melos run test | tail -50` enmascaran el exit code del comando upstream. Sin pipefail, un fallo de melos se reporta como success. Validado en Fase 0
|
||||
- **Release notes en prod**: actualmente se hace manual. Si automatizamos via `android_release.sh` (estilo effi), las release notes vienen de la env var `RELEASE_NOTES`. Cambiar la convención del equipo o seguir manual via Play Console UI
|
||||
|
||||
## Referencias
|
||||
|
||||
- Setup base: `~/Desktop/apps/effi-app-flutter/codemagic.yaml` + `codemagic_scripts/`
|
||||
- Release workflow manual actual: `memory/project_savefamily_release_workflow.md`
|
||||
- Apple Dev / Account Holder: `memory/project_apple_developer_team.md`, `memory/project_apns_setup.md`
|
||||
- App Store / Play Store IDs: `memory/project_app_store_ids.md`
|
||||
- iOS deployment target: `memory/project_ios_deployment_target.md`
|
||||
- AppDelegate Antelop caveat: `memory/project_appdelegate_antelop_firebase.md`
|
||||
Reference in New Issue
Block a user