@daemux/store-automator 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/package.json +1 -1
  3. package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
  4. package/plugins/store-automator/agents/appstore-meta-creator.md +13 -8
  5. package/plugins/store-automator/agents/architect.md +42 -1
  6. package/plugins/store-automator/agents/devops.md +26 -27
  7. package/scripts/check_google_play.py +1 -1
  8. package/scripts/manage_version_ios.py +1 -1
  9. package/src/install-paths.mjs +2 -3
  10. package/src/templates.mjs +2 -2
  11. package/templates/CLAUDE.md.template +70 -16
  12. package/templates/codemagic.template.yaml +131 -96
  13. package/templates/fastlane/android/Fastfile.template +21 -3
  14. package/templates/fastlane/ios/Fastfile.template +4 -3
  15. package/templates/github/workflows/android-release.yml +108 -39
  16. package/templates/github/workflows/codemagic-trigger.yml +15 -5
  17. package/templates/github/workflows/ios-release.yml +94 -37
  18. package/templates/scripts/check_google_play.py +1 -1
  19. package/templates/scripts/ci/android/check-readiness.sh +18 -24
  20. package/templates/scripts/ci/android/manage-version.sh +17 -16
  21. package/templates/scripts/ci/android/sync-iap.sh +6 -6
  22. package/templates/scripts/ci/android/update-data-safety.sh +2 -2
  23. package/templates/scripts/ci/android/upload-metadata.sh +23 -5
  24. package/templates/scripts/ci/common/install-fastlane.sh +1 -1
  25. package/templates/scripts/ci/common/link-fastlane.sh +1 -1
  26. package/templates/scripts/ci/common/read-config.sh +3 -0
  27. package/templates/scripts/ci/ios/build.sh +39 -0
  28. package/templates/scripts/ci/ios/set-build-number.sh +17 -20
  29. package/templates/scripts/ci/ios/setup-signing.sh +48 -7
  30. package/templates/scripts/ci/ios/sync-iap.sh +6 -17
  31. package/templates/scripts/ci/ios/upload-binary.sh +0 -1
  32. package/templates/scripts/ci/ios/upload-metadata.sh +3 -13
  33. package/templates/scripts/create_app_record.py +1 -1
  34. package/templates/scripts/manage_version_ios.py +1 -1
  35. package/templates/scripts/update_data_safety.py +1 -1
@@ -34,17 +34,19 @@ workflows:
34
34
  scripts:
35
35
  - name: Link Fastlane directories
36
36
  script: |
37
+ set -euo pipefail
37
38
  ln -sfn "$CM_BUILD_DIR/fastlane/ios" "$CM_BUILD_DIR/$APP_ROOT/ios/fastlane"
38
39
  ln -sfn "$CM_BUILD_DIR/fastlane" "$CM_BUILD_DIR/$APP_ROOT/fastlane"
39
40
 
40
41
  - name: Ensure CERTIFICATE_PRIVATE_KEY
41
42
  script: |
43
+ set -euo pipefail
42
44
  KEY_FILE="$CM_BUILD_DIR/creds/ios_dist_private_key"
43
45
  if [ -f "$KEY_FILE" ]; then
44
46
  echo "Using CERTIFICATE_PRIVATE_KEY from repo creds/"
45
47
  else
46
- echo "ERROR: creds/ios_dist_private_key not found in repo."
47
- echo "Generate it with: ssh-keygen -t rsa -b 2048 -m PEM -f creds/ios_dist_private_key -q -N ''"
48
+ echo "ERROR: creds/ios_dist_private_key not found in repo." >&2
49
+ echo "Generate it with: ssh-keygen -t rsa -b 2048 -m PEM -f creds/ios_dist_private_key -q -N ''" >&2
48
50
  exit 1
49
51
  fi
50
52
  echo "CERTIFICATE_PRIVATE_KEY<<DELIMITER" >> $CM_ENV
@@ -54,6 +56,7 @@ workflows:
54
56
 
55
57
  - name: Set up App Store Connect API key
56
58
  script: |
59
+ set -euo pipefail
57
60
  echo "APP_STORE_CONNECT_KEY_IDENTIFIER=$APPLE_KEY_ID" >> $CM_ENV
58
61
  echo "APP_STORE_CONNECT_ISSUER_ID=$APPLE_ISSUER_ID" >> $CM_ENV
59
62
  echo "APP_STORE_CONNECT_PRIVATE_KEY<<KEYDELIMITER" >> $CM_ENV
@@ -67,54 +70,70 @@ workflows:
67
70
 
68
71
  - name: Install Fastlane
69
72
  script: |
73
+ set -euo pipefail
70
74
  cd $CM_BUILD_DIR/$APP_ROOT/ios
71
75
  gem install bundler
72
76
  bundle install
73
77
 
74
78
  - name: Ensure app record exists
75
79
  script: |
80
+ set -euo pipefail
76
81
  echo "=== Checking if $BUNDLE_ID exists in App Store Connect ==="
77
82
 
78
83
  # First try: list apps filtering by bundle ID
79
84
  echo "--- Running: app-store-connect apps list --bundle-id-identifier $BUNDLE_ID ---"
80
- APP_JSON=$(app-store-connect apps list \
85
+ if ! APP_JSON=$(app-store-connect apps list \
81
86
  --bundle-id-identifier "$BUNDLE_ID" \
82
87
  --strict-match-identifier \
83
- --json 2>/tmp/asc_apps_err.log || true)
88
+ --json 2>/tmp/asc_apps_err.log); then
89
+ echo "ERROR: Failed to query App Store Connect" >&2
90
+ cat /tmp/asc_apps_err.log >&2 2>/dev/null || true
91
+ exit 1
92
+ fi
84
93
 
85
- echo "Apps list stderr:"
86
- cat /tmp/asc_apps_err.log 2>/dev/null || true
87
94
  echo "Apps list stdout (first 500 chars):"
88
95
  echo "$APP_JSON" | head -c 500
89
96
 
90
- EXISTING=$(echo "$APP_JSON" | python3 -c "import sys,json; data=json.load(sys.stdin); apps=data if isinstance(data,list) else []; print('yes' if len(apps)>0 else 'no')" 2>/dev/null || echo "no")
97
+ EXISTING=$(echo "$APP_JSON" | python3 -c "
98
+ import sys, json
99
+ data = json.load(sys.stdin)
100
+ apps = data if isinstance(data, list) else []
101
+ print('yes' if len(apps) > 0 else 'no')
102
+ ")
91
103
 
92
104
  echo "App exists result: $EXISTING"
93
105
 
94
106
  if [ "$EXISTING" = "yes" ]; then
95
107
  echo "App record found for $BUNDLE_ID"
96
108
  else
97
- echo ""
98
- echo "=========================================="
99
- echo "BUILD STOPPED - APP RECORD NOT DETECTED"
100
- echo "=========================================="
101
- echo ""
102
- echo "The app-store-connect CLI could not find $BUNDLE_ID."
103
- echo "If you already created the app in App Store Connect,"
104
- echo "this may be an API propagation delay or permission issue."
105
- echo ""
106
- echo "Retrying with verbose output..."
107
- app-store-connect apps list --json 2>&1 | python3 -c "import sys,json;data=json.load(sys.stdin);apps=data if isinstance(data,list) else [];print(f'Total apps: {len(apps)}');[print(f' App: {a.get(\"attributes\",{}).get(\"name\",\"?\")} | Bundle: {a.get(\"attributes\",{}).get(\"bundleId\",\"?\")} | ID: {a.get(\"id\",\"?\")}') for a in apps[:10]]" 2>&1 || true
109
+ echo "ERROR: App record not found for $BUNDLE_ID" >&2
110
+ echo "If you already created the app in App Store Connect," >&2
111
+ echo "this may be an API propagation delay or permission issue." >&2
112
+ echo "" >&2
113
+ echo "Listing all visible apps for debugging:" >&2
114
+ app-store-connect apps list --json 2>&1 | python3 -c "
115
+ import sys, json
116
+ data = json.load(sys.stdin)
117
+ apps = data if isinstance(data, list) else []
118
+ print(f'Total apps: {len(apps)}')
119
+ for a in apps[:10]:
120
+ attrs = a.get('attributes', {})
121
+ name = attrs.get('name', '?')
122
+ bid = attrs.get('bundleId', '?')
123
+ print(f' {name} | {bid} | {a.get(\"id\", \"?\")}')
124
+ " 2>&1 || true
108
125
  exit 1
109
126
  fi
110
127
 
111
128
  - name: Upload iOS metadata and screenshots
112
129
  script: |
130
+ set -euo pipefail
113
131
  cd $CM_BUILD_DIR/$APP_ROOT/ios
114
132
  bundle exec fastlane upload_metadata_ios
115
133
 
116
134
  - name: Sync IAP and subscriptions
117
135
  script: |
136
+ set -euo pipefail
118
137
  FORCE_FLAG=""
119
138
  if [ ! -f "$CM_BUILD_DIR/.codemagic/ios_iap_synced" ]; then
120
139
  FORCE_FLAG="--force"
@@ -139,6 +158,7 @@ workflows:
139
158
 
140
159
  - name: Set up iOS code signing
141
160
  script: |
161
+ set -euo pipefail
142
162
  keychain initialize
143
163
 
144
164
  # Attempt to fetch or create signing files.
@@ -189,7 +209,7 @@ workflows:
189
209
  # Verify signing artifacts exist
190
210
  P12_COUNT=$(ls /Users/builder/Library/MobileDevice/Certificates/*.p12 2>/dev/null | wc -l)
191
211
  if [ "$P12_COUNT" -eq 0 ]; then
192
- echo "ERROR: No .p12 certificates found after signing setup"
212
+ echo "ERROR: No .p12 certificates found after signing setup" >&2
193
213
  exit 1
194
214
  fi
195
215
 
@@ -198,11 +218,19 @@ workflows:
198
218
 
199
219
  - name: Manage iOS version
200
220
  script: |
201
- pip3 install PyJWT cryptography requests
202
- VERSION_JSON=$(python3 scripts/manage_version_ios.py 2>/dev/null || echo '{"version":"1.0.0","version_id":"","state":"NEW"}')
203
- APP_VERSION=$(echo "$VERSION_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['version'])")
204
- APP_VERSION_ID=$(echo "$VERSION_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('version_id',''))")
205
- APP_STATUS=$(echo "$VERSION_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('state','NEW'))")
221
+ set -euo pipefail
222
+ pip3 install --break-system-packages PyJWT cryptography requests
223
+ VERSION_JSON=$(python3 scripts/manage_version_ios.py)
224
+ eval "$(echo "$VERSION_JSON" | python3 -c "
225
+ import sys, json
226
+ d = json.load(sys.stdin)
227
+ v = d['version']
228
+ vid = d.get('version_id', '')
229
+ st = d.get('state', 'NEW')
230
+ print(f\"APP_VERSION='{v}'\")
231
+ print(f\"APP_VERSION_ID='{vid}'\")
232
+ print(f\"APP_STATUS='{st}'\")
233
+ ")"
206
234
  echo "APP_VERSION=$APP_VERSION" >> $CM_ENV
207
235
  echo "APP_VERSION_ID=$APP_VERSION_ID" >> $CM_ENV
208
236
  echo "APP_STATUS=$APP_STATUS" >> $CM_ENV
@@ -210,7 +238,8 @@ workflows:
210
238
 
211
239
  - name: Set Flutter version
212
240
  script: |
213
- BUILD_NUMBER=$(($(app-store-connect get-latest-app-store-build-number "$BUNDLE_ID" 2>/dev/null || echo "0") + 1))
241
+ set -euo pipefail
242
+ BUILD_NUMBER=$(($(app-store-connect get-latest-app-store-build-number "$BUNDLE_ID") + 1))
214
243
  if [ -z "$APP_VERSION" ]; then
215
244
  APP_VERSION="1.0.0"
216
245
  fi
@@ -222,13 +251,21 @@ workflows:
222
251
  echo "Building: $APP_VERSION+$BUILD_NUMBER"
223
252
 
224
253
  - name: Flutter packages
225
- script: cd $CM_BUILD_DIR/$APP_ROOT && flutter pub get
254
+ script: |
255
+ set -euo pipefail
256
+ cd $CM_BUILD_DIR/$APP_ROOT && flutter pub get
226
257
 
227
258
  - name: Build iOS
228
- script: cd $CM_BUILD_DIR/$APP_ROOT && flutter build ipa --release --export-options-plist=/Users/builder/export_options.plist
259
+ script: |
260
+ set -euo pipefail
261
+ cd $CM_BUILD_DIR/$APP_ROOT
262
+ flutter build ipa \
263
+ --release \
264
+ --export-options-plist=/Users/builder/export_options.plist
229
265
 
230
266
  - name: Upload IPA to App Store
231
267
  script: |
268
+ set -euo pipefail
232
269
  cd $CM_BUILD_DIR/$APP_ROOT/ios
233
270
  bundle exec fastlane upload_binary_ios
234
271
 
@@ -272,11 +309,13 @@ workflows:
272
309
  scripts:
273
310
  - name: Link Fastlane directories
274
311
  script: |
312
+ set -euo pipefail
275
313
  ln -sfn "$CM_BUILD_DIR/fastlane/android" "$CM_BUILD_DIR/$APP_ROOT/android/fastlane"
276
314
  ln -sfn "$CM_BUILD_DIR/fastlane" "$CM_BUILD_DIR/$APP_ROOT/fastlane"
277
315
 
278
316
  - name: Ensure Android upload keystore
279
317
  script: |
318
+ set -euo pipefail
280
319
  KEYSTORE_PATH="$CM_BUILD_DIR/$APP_ROOT/android/upload.keystore"
281
320
  CREDS_PATH="$CM_BUILD_DIR/creds/android_upload.keystore"
282
321
  GENERATED=false
@@ -320,33 +359,37 @@ workflows:
320
359
 
321
360
  - name: Check Google Play readiness
322
361
  script: |
362
+ set -euo pipefail
323
363
  echo "Checking Google Play setup status..."
324
- pip3 install PyJWT cryptography requests
364
+ pip3 install --break-system-packages PyJWT cryptography requests
325
365
  export SA_JSON="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
326
366
  export PACKAGE_NAME="$PACKAGE_NAME"
327
- RESULT=$(python3 scripts/check_google_play.py 2>/dev/null || echo '{"ready":false}')
328
- READY=$(echo "$RESULT" | python3 -c "import sys,json; print(str(json.load(sys.stdin).get('ready',False)).lower())" 2>/dev/null || echo "false")
367
+ RESULT=$(python3 scripts/check_google_play.py)
368
+ READY=$(echo "$RESULT" | python3 -c "
369
+ import sys, json
370
+ print(str(json.load(sys.stdin).get('ready', False)).lower())
371
+ ")
329
372
  if [ "$READY" != "true" ]; then
330
373
  echo "GOOGLE_PLAY_READY=false" >> $CM_ENV
331
- printf '%s\n' \
332
- "# Google Play Setup - Manual Steps Required" \
333
- "" \
334
- "Your Android AAB is available in the build artifacts above." \
335
- "" \
336
- "## Steps to complete" \
337
- "" \
338
- "1. **Go to Google Play Console** - https://play.google.com/console" \
339
- "2. **Create your app** (if not already created) with the correct package name" \
340
- "3. **Upload the AAB** from build artifacts to an internal testing track" \
341
- "4. **Complete the Store Listing** - Add title, descriptions, screenshots, and app icon" \
342
- "5. **Complete the Content Rating** questionnaire" \
343
- "6. **Set up Pricing and Distribution**" \
344
- "7. **Complete the Data Safety** form" \
345
- "8. **Review and roll out** the internal testing release" \
346
- "" \
347
- "## After completing all steps" \
348
- "Just git push again - Codemagic will publish automatically on subsequent builds." \
349
- > "$CM_BUILD_DIR/HOW_TO_GOOGLE_PLAY.md"
374
+ cat > "$CM_BUILD_DIR/HOW_TO_GOOGLE_PLAY.md" <<'GUIDE'
375
+ # Google Play Setup - Manual Steps Required
376
+
377
+ Your Android AAB is available in the build artifacts above.
378
+
379
+ ## Steps to complete
380
+
381
+ 1. **Go to Google Play Console** - https://play.google.com/console
382
+ 2. **Create your app** (if not already created)
383
+ 3. **Upload the AAB** from build artifacts to an internal testing track
384
+ 4. **Complete the Store Listing** - title, descriptions, screenshots, icon
385
+ 5. **Complete the Content Rating** questionnaire
386
+ 6. **Set up Pricing and Distribution**
387
+ 7. **Complete the Data Safety** form
388
+ 8. **Review and roll out** the internal testing release
389
+
390
+ ## After completing all steps
391
+ Just git push again - Codemagic will publish automatically.
392
+ GUIDE
350
393
  echo "Google Play not ready - see HOW_TO_GOOGLE_PLAY.md in artifacts"
351
394
  else
352
395
  echo "GOOGLE_PLAY_READY=true" >> $CM_ENV
@@ -363,31 +406,28 @@ workflows:
363
406
 
364
407
  - name: Install Fastlane
365
408
  script: |
409
+ set -euo pipefail
366
410
  cd $CM_BUILD_DIR/$APP_ROOT/android
367
411
  gem install bundler
368
412
  bundle install
369
413
 
370
414
  - name: Upload Android metadata and screenshots
371
415
  script: |
416
+ set -euo pipefail
372
417
  if [ "$GOOGLE_PLAY_READY" != "true" ]; then
373
- echo "WARNING: Google Play not ready - skipping metadata upload."
374
- echo "First AAB must be uploaded manually before metadata can sync."
418
+ echo "Google Play not ready - skipping metadata upload."
375
419
  exit 0
376
420
  fi
377
421
  export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
378
422
  cd $CM_BUILD_DIR/$APP_ROOT/android
379
- if bundle exec fastlane upload_metadata_android; then
380
- echo "Metadata uploaded successfully"
381
- else
382
- echo "WARNING: Metadata upload failed (app may still be in draft state)."
383
- echo "Metadata will sync on next build after app leaves draft."
384
- echo "Continuing build..."
385
- fi
423
+ bundle exec fastlane upload_metadata_android
424
+ echo "Metadata uploaded successfully"
386
425
 
387
426
  - name: Sync subscriptions and IAP
388
427
  script: |
428
+ set -euo pipefail
389
429
  if [ "$GOOGLE_PLAY_READY" != "true" ]; then
390
- echo "WARNING: Google Play not ready - skipping IAP sync."
430
+ echo "Google Play not ready - skipping IAP sync."
391
431
  exit 0
392
432
  fi
393
433
  FORCE_FLAG=""
@@ -397,21 +437,17 @@ workflows:
397
437
  if ./scripts/check_changed.sh $FORCE_FLAG fastlane/iap_config.json; then
398
438
  export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
399
439
  cd $CM_BUILD_DIR/$APP_ROOT/android
400
- if bundle exec fastlane sync_google_iap 2>&1; then
401
- echo "IAP sync completed successfully"
402
- else
403
- echo "WARNING: IAP sync failed (fastlane-plugin-iap may not be available yet)."
404
- echo "IAP products can be configured manually in Google Play Console."
405
- echo "Continuing build..."
406
- fi
440
+ bundle exec fastlane sync_google_iap
441
+ echo "IAP sync completed successfully"
407
442
  else
408
443
  echo "IAP config unchanged - skipping"
409
444
  fi
410
445
 
411
446
  - name: Update data safety form
412
447
  script: |
448
+ set -euo pipefail
413
449
  if [ "$GOOGLE_PLAY_READY" != "true" ]; then
414
- echo "WARNING: Google Play not ready - skipping data safety update."
450
+ echo "Google Play not ready - skipping data safety update."
415
451
  exit 0
416
452
  fi
417
453
  FORCE_FLAG=""
@@ -421,13 +457,8 @@ workflows:
421
457
  if ./scripts/check_changed.sh $FORCE_FLAG fastlane/data_safety.csv; then
422
458
  export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
423
459
  cd $CM_BUILD_DIR/$APP_ROOT/android
424
- if bundle exec fastlane update_data_safety 2>&1; then
425
- echo "Data safety form updated successfully"
426
- else
427
- echo "WARNING: Data safety update failed."
428
- echo "Data safety can be configured manually in Google Play Console."
429
- echo "Continuing build..."
430
- fi
460
+ bundle exec fastlane update_data_safety
461
+ echo "Data safety form updated successfully"
431
462
  else
432
463
  echo "Data safety unchanged - skipping"
433
464
  fi
@@ -444,16 +475,22 @@ workflows:
444
475
 
445
476
  - name: Manage Android version
446
477
  script: |
478
+ set -euo pipefail
447
479
  # Read iOS version for consistency (if iOS workflow ran)
448
- pip3 install PyJWT cryptography requests
480
+ pip3 install --break-system-packages PyJWT cryptography requests
449
481
  echo "APP_STORE_CONNECT_KEY_IDENTIFIER=$APPLE_KEY_ID" >> $CM_ENV
450
482
  echo "APP_STORE_CONNECT_ISSUER_ID=$APPLE_ISSUER_ID" >> $CM_ENV
451
483
  echo "APP_STORE_CONNECT_PRIVATE_KEY<<KEYDELIMITER" >> $CM_ENV
452
484
  cat "$CM_BUILD_DIR/$P8_KEY_PATH" >> $CM_ENV
453
485
  echo "" >> $CM_ENV
454
486
  echo "KEYDELIMITER" >> $CM_ENV
455
- VERSION_JSON=$(python3 scripts/manage_version_ios.py 2>/dev/null || echo '{"version":"1.0.0","version_id":"","state":"NEW"}')
456
- APP_VERSION=$(echo "$VERSION_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['version'])")
487
+ VERSION_JSON=$(python3 scripts/manage_version_ios.py)
488
+ eval "$(echo "$VERSION_JSON" | python3 -c "
489
+ import sys, json
490
+ d = json.load(sys.stdin)
491
+ v = d['version']
492
+ print(f\"APP_VERSION='{v}'\")
493
+ ")"
457
494
  echo "APP_VERSION=$APP_VERSION" >> $CM_ENV
458
495
  if [ "$GOOGLE_PLAY_READY" != "true" ]; then
459
496
  LATEST_BUILD=0
@@ -461,14 +498,13 @@ workflows:
461
498
  export GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS="$(cat $CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH)"
462
499
  LATEST_BUILD_OUTPUT=$(google-play get-latest-build-number \
463
500
  --package-name "$PACKAGE_NAME" \
464
- --tracks=production,beta,alpha,internal 2>/dev/null || true)
501
+ --tracks=production,beta,alpha,internal)
465
502
  # Extract just the number from output
466
503
  LATEST_BUILD=$(echo "$LATEST_BUILD_OUTPUT" | grep -oE '^[0-9]+$' | tail -1)
467
504
  if [ -z "$LATEST_BUILD" ]; then
468
- echo "WARNING: Could not get latest build number from Google Play."
469
- echo "Raw output: $LATEST_BUILD_OUTPUT"
470
- echo "Falling back to build number 1"
471
- LATEST_BUILD=1
505
+ echo "ERROR: Could not get latest build number from Google Play." >&2
506
+ echo "Raw output: $LATEST_BUILD_OUTPUT" >&2
507
+ exit 1
472
508
  fi
473
509
  echo "Latest build number from Google Play: $LATEST_BUILD"
474
510
  fi
@@ -485,26 +521,25 @@ workflows:
485
521
  echo "Android versionCode: $NEW_BUILD, versionName: $APP_VERSION"
486
522
 
487
523
  - name: Flutter packages
488
- script: cd $CM_BUILD_DIR/$APP_ROOT && flutter pub get
524
+ script: |
525
+ set -euo pipefail
526
+ cd $CM_BUILD_DIR/$APP_ROOT && flutter pub get
489
527
 
490
528
  - name: Build Android
491
- script: cd $CM_BUILD_DIR/$APP_ROOT && flutter build appbundle --release
529
+ script: |
530
+ set -euo pipefail
531
+ cd $CM_BUILD_DIR/$APP_ROOT && flutter build appbundle --release
492
532
 
493
533
  - name: Upload AAB to Google Play
494
534
  script: |
535
+ set -euo pipefail
495
536
  if [ "$GOOGLE_PLAY_READY" != "true" ]; then
496
- echo "============================================"
497
- echo "FIRST RUN: AAB built but NOT uploaded."
498
- echo "============================================"
499
- echo ""
500
- echo "Download the AAB from build artifacts and"
501
- echo "upload it manually to Google Play Console."
502
- echo "See HOW_TO_GOOGLE_PLAY.md in artifacts for"
503
- echo "step-by-step instructions."
504
- echo ""
505
- echo "After completing manual setup, push again"
506
- echo "for fully automated publishing."
507
- echo "============================================"
537
+ cat >&2 <<'MSG'
538
+ FIRST RUN: AAB built but NOT uploaded.
539
+ Download the AAB from build artifacts and upload it manually
540
+ to Google Play Console. See HOW_TO_GOOGLE_PLAY.md in artifacts.
541
+ After completing manual setup, push again for automated publishing.
542
+ MSG
508
543
  exit 1
509
544
  fi
510
545
  export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
@@ -3,9 +3,10 @@ default_platform(:android)
3
3
  # Resolve paths absolutely to avoid symlink-relative breakage.
4
4
  # CM_BUILD_DIR is set by Codemagic CI; locally we derive from __FILE__.
5
5
  ROOT_DIR = ENV.fetch("CM_BUILD_DIR", File.expand_path("../..", __FILE__))
6
- APP_ROOT = ENV.fetch("APP_ROOT", "app")
6
+ APP_DIR = ENV.fetch("APP_ROOT", "#{ROOT_DIR}/app")
7
+ APP_DIR = "#{ROOT_DIR}/#{APP_DIR}" unless APP_DIR.start_with?("/")
7
8
 
8
- AAB_PATH = "#{ROOT_DIR}/#{APP_ROOT}/build/app/outputs/bundle/release/app-release.aab"
9
+ AAB_PATH = "#{APP_DIR}/build/app/outputs/bundle/release/app-release.aab"
9
10
 
10
11
  def metadata_changed?(path)
11
12
  !sh("git diff --name-only HEAD~1 -- #{path}").strip.empty?
@@ -46,15 +47,32 @@ platform :android do
46
47
  end
47
48
 
48
49
  lane :upload_metadata_android do
50
+ # supply always calls fetch_track_and_release! even for metadata-only
51
+ # uploads. When the track contains multiple releases and no version_code
52
+ # is given it errors with "more than one release found". We resolve this
53
+ # by fetching the latest version code from the track first.
54
+ track = ENV.fetch("TRACK", "internal")
55
+ codes = google_play_track_version_codes(
56
+ package_name: ENV["PACKAGE_NAME"],
57
+ json_key: ENV["GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH"],
58
+ track: track
59
+ )
60
+ latest_code = codes.max
61
+
62
+ # Draft apps only accept draft releases. Use RELEASE_STATUS env var so
63
+ # this keeps working after the first production release.
64
+ status = ENV.fetch("RELEASE_STATUS", "draft")
65
+
49
66
  upload_to_play_store(
50
67
  base_play_store_options.merge(
68
+ version_code: latest_code,
69
+ release_status: status,
51
70
  skip_upload_aab: true,
52
71
  skip_upload_apk: true,
53
72
  skip_upload_metadata: false,
54
73
  skip_upload_screenshots: false,
55
74
  skip_upload_images: false,
56
75
  skip_upload_changelogs: true,
57
- release_status: "draft",
58
76
  metadata_path: "#{ROOT_DIR}/fastlane/metadata/android"
59
77
  )
60
78
  )
@@ -3,7 +3,8 @@ default_platform(:ios)
3
3
  # Resolve paths absolutely to avoid symlink-relative breakage.
4
4
  # CM_BUILD_DIR is set by Codemagic CI; locally we derive from __FILE__.
5
5
  ROOT_DIR = ENV.fetch("CM_BUILD_DIR", File.expand_path("../..", __FILE__))
6
- APP_ROOT = ENV.fetch("APP_ROOT", "app")
6
+ APP_DIR = ENV.fetch("APP_ROOT", "#{ROOT_DIR}/app")
7
+ APP_DIR = "#{ROOT_DIR}/#{APP_DIR}" unless APP_DIR.start_with?("/")
7
8
 
8
9
  # Monkey-patch: Fastlane deliver crashes with "No data" when fetching
9
10
  # app_store_review_detail on the first version (fastlane/fastlane#20538).
@@ -74,7 +75,7 @@ platform :ios do
74
75
  lane :deploy_ios do
75
76
  deliver(
76
77
  metadata_deliver_options.merge(submission_options).merge(
77
- ipa: Dir.glob("#{ROOT_DIR}/#{APP_ROOT}/build/ios/ipa/*.ipa").first,
78
+ ipa: Dir.glob("#{APP_DIR}/build/ios/ipa/*.ipa").first,
78
79
  app_rating_config_path: "#{ROOT_DIR}/fastlane/app_rating_config.json",
79
80
  skip_metadata: !metadata_changed?("fastlane/metadata/ios/"),
80
81
  skip_screenshots: !metadata_changed?("fastlane/screenshots/ios/"),
@@ -99,7 +100,7 @@ platform :ios do
99
100
  lane :upload_binary_ios do
100
101
  deliver(
101
102
  base_deliver_options.merge(submission_options).merge(
102
- ipa: Dir.glob("#{ROOT_DIR}/#{APP_ROOT}/build/ios/ipa/*.ipa").first,
103
+ ipa: Dir.glob("#{APP_DIR}/build/ios/ipa/*.ipa").first,
103
104
  skip_metadata: true,
104
105
  skip_screenshots: true,
105
106
  run_precheck_before_submit: true,