@daemux/store-automator 0.9.0 → 0.10.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.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +6 -24
- package/bin/cli.mjs +7 -66
- package/package.json +1 -2
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/plugins/store-automator/agents/architect.md +1 -1
- package/plugins/store-automator/agents/devops.md +28 -75
- package/plugins/store-automator/agents/product-manager.md +5 -5
- package/scripts/check_changed.sh +1 -1
- package/src/ci-config.mjs +0 -10
- package/src/install-paths.mjs +1 -75
- package/src/install.mjs +8 -19
- package/src/mcp-setup.mjs +1 -35
- package/src/prompt.mjs +1 -19
- package/src/templates.mjs +0 -1
- package/src/uninstall.mjs +0 -4
- package/src/utils.mjs +0 -9
- package/templates/CLAUDE.md.template +18 -18
- package/templates/ci.config.yaml.template +0 -11
- package/templates/fastlane/android/Fastfile.template +2 -2
- package/templates/fastlane/android/Pluginfile.template +1 -1
- package/templates/fastlane/ios/Fastfile.template +2 -2
- package/templates/fastlane/ios/Pluginfile.template +1 -1
- package/templates/fastlane/ios/Snapfile.template +1 -1
- package/templates/github/workflows/android-release.yml +0 -4
- package/templates/github/workflows/ios-release.yml +0 -4
- package/templates/scripts/check_changed.sh +1 -1
- package/templates/scripts/ci/android/manage-version.sh +16 -17
- package/templates/scripts/ci/android/sync-iap.sh +4 -4
- package/templates/scripts/ci/android/update-data-safety.sh +21 -8
- package/templates/scripts/ci/android/upload-metadata.sh +7 -5
- package/templates/scripts/ci/common/install-fastlane.sh +14 -0
- package/templates/scripts/ci/common/link-fastlane.sh +1 -1
- package/templates/scripts/ci/common/read-config.sh +1 -5
- package/templates/scripts/ci/ios/build.sh +4 -10
- package/templates/scripts/ci/ios/set-build-number.sh +20 -17
- package/templates/scripts/ci/ios/setup-signing.sh +0 -9
- package/templates/scripts/ci/ios/sync-iap.sh +4 -4
- package/templates/scripts/update_data_safety.py +14 -10
- package/scripts/codemagic-setup.mjs +0 -44
- package/scripts/generate.sh +0 -107
- package/src/codemagic-api.mjs +0 -75
- package/src/codemagic-setup.mjs +0 -190
- package/src/github-setup.mjs +0 -43
- package/templates/codemagic.template.yaml +0 -551
- package/templates/github/workflows/codemagic-trigger.yml +0 -78
- package/templates/scripts/generate.sh +0 -107
|
@@ -1,551 +0,0 @@
|
|
|
1
|
-
workflows:
|
|
2
|
-
ios-release:
|
|
3
|
-
name: iOS Release
|
|
4
|
-
max_build_duration: 90
|
|
5
|
-
instance_type: mac_mini_m4
|
|
6
|
-
environment:
|
|
7
|
-
flutter: stable
|
|
8
|
-
xcode: latest
|
|
9
|
-
vars:
|
|
10
|
-
BUNDLE_ID: "${BUNDLE_ID}"
|
|
11
|
-
APP_NAME: "${APP_NAME}"
|
|
12
|
-
SKU: "${SKU}"
|
|
13
|
-
APPLE_ID: "${APPLE_ID}"
|
|
14
|
-
P8_KEY_PATH: "${P8_KEY_PATH}"
|
|
15
|
-
APPLE_KEY_ID: "${APPLE_KEY_ID}"
|
|
16
|
-
APPLE_ISSUER_ID: "${APPLE_ISSUER_ID}"
|
|
17
|
-
PRIMARY_CATEGORY: "${PRIMARY_CATEGORY}"
|
|
18
|
-
SECONDARY_CATEGORY: "${SECONDARY_CATEGORY}"
|
|
19
|
-
PRICE_TIER: "${PRICE_TIER}"
|
|
20
|
-
SUBMIT_FOR_REVIEW: "${SUBMIT_FOR_REVIEW}"
|
|
21
|
-
AUTOMATIC_RELEASE: "${AUTOMATIC_RELEASE}"
|
|
22
|
-
FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS: "1"
|
|
23
|
-
APP_ROOT: "${APP_ROOT}"
|
|
24
|
-
cache:
|
|
25
|
-
cache_paths:
|
|
26
|
-
- $HOME/.gem
|
|
27
|
-
- ${APP_ROOT}/ios/vendor/bundle
|
|
28
|
-
triggering:
|
|
29
|
-
events:
|
|
30
|
-
- push
|
|
31
|
-
branch_patterns:
|
|
32
|
-
- pattern: main
|
|
33
|
-
include: true
|
|
34
|
-
scripts:
|
|
35
|
-
- name: Link Fastlane directories
|
|
36
|
-
script: |
|
|
37
|
-
set -euo pipefail
|
|
38
|
-
ln -sfn "$CM_BUILD_DIR/fastlane/ios" "$CM_BUILD_DIR/$APP_ROOT/ios/fastlane"
|
|
39
|
-
ln -sfn "$CM_BUILD_DIR/fastlane" "$CM_BUILD_DIR/$APP_ROOT/fastlane"
|
|
40
|
-
|
|
41
|
-
- name: Ensure CERTIFICATE_PRIVATE_KEY
|
|
42
|
-
script: |
|
|
43
|
-
set -euo pipefail
|
|
44
|
-
KEY_FILE="$CM_BUILD_DIR/creds/ios_dist_private_key"
|
|
45
|
-
if [ -f "$KEY_FILE" ]; then
|
|
46
|
-
echo "Using CERTIFICATE_PRIVATE_KEY from repo creds/"
|
|
47
|
-
else
|
|
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
|
|
50
|
-
exit 1
|
|
51
|
-
fi
|
|
52
|
-
echo "CERTIFICATE_PRIVATE_KEY<<DELIMITER" >> $CM_ENV
|
|
53
|
-
cat "$KEY_FILE" >> $CM_ENV
|
|
54
|
-
echo "" >> $CM_ENV
|
|
55
|
-
echo "DELIMITER" >> $CM_ENV
|
|
56
|
-
|
|
57
|
-
- name: Set up App Store Connect API key
|
|
58
|
-
script: |
|
|
59
|
-
set -euo pipefail
|
|
60
|
-
echo "APP_STORE_CONNECT_KEY_IDENTIFIER=$APPLE_KEY_ID" >> $CM_ENV
|
|
61
|
-
echo "APP_STORE_CONNECT_ISSUER_ID=$APPLE_ISSUER_ID" >> $CM_ENV
|
|
62
|
-
echo "APP_STORE_CONNECT_PRIVATE_KEY<<KEYDELIMITER" >> $CM_ENV
|
|
63
|
-
cat "$CM_BUILD_DIR/$P8_KEY_PATH" >> $CM_ENV
|
|
64
|
-
echo "" >> $CM_ENV
|
|
65
|
-
echo "KEYDELIMITER" >> $CM_ENV
|
|
66
|
-
# Write P8 key to temp file once for all Fastlane steps
|
|
67
|
-
P8_TMP="/tmp/fastlane_api_key.p8"
|
|
68
|
-
cat "$CM_BUILD_DIR/$P8_KEY_PATH" > "$P8_TMP"
|
|
69
|
-
echo "FASTLANE_API_KEY_PATH=$P8_TMP" >> $CM_ENV
|
|
70
|
-
|
|
71
|
-
- name: Install Fastlane
|
|
72
|
-
script: |
|
|
73
|
-
set -euo pipefail
|
|
74
|
-
cd $CM_BUILD_DIR/$APP_ROOT/ios
|
|
75
|
-
gem install bundler
|
|
76
|
-
bundle install
|
|
77
|
-
|
|
78
|
-
- name: Ensure app record exists
|
|
79
|
-
script: |
|
|
80
|
-
set -euo pipefail
|
|
81
|
-
echo "=== Checking if $BUNDLE_ID exists in App Store Connect ==="
|
|
82
|
-
|
|
83
|
-
# First try: list apps filtering by bundle ID
|
|
84
|
-
echo "--- Running: app-store-connect apps list --bundle-id-identifier $BUNDLE_ID ---"
|
|
85
|
-
if ! APP_JSON=$(app-store-connect apps list \
|
|
86
|
-
--bundle-id-identifier "$BUNDLE_ID" \
|
|
87
|
-
--strict-match-identifier \
|
|
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
|
|
93
|
-
|
|
94
|
-
echo "Apps list stdout (first 500 chars):"
|
|
95
|
-
echo "$APP_JSON" | head -c 500
|
|
96
|
-
|
|
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
|
-
")
|
|
103
|
-
|
|
104
|
-
echo "App exists result: $EXISTING"
|
|
105
|
-
|
|
106
|
-
if [ "$EXISTING" = "yes" ]; then
|
|
107
|
-
echo "App record found for $BUNDLE_ID"
|
|
108
|
-
else
|
|
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
|
|
125
|
-
exit 1
|
|
126
|
-
fi
|
|
127
|
-
|
|
128
|
-
- name: Upload iOS metadata and screenshots
|
|
129
|
-
script: |
|
|
130
|
-
set -euo pipefail
|
|
131
|
-
cd $CM_BUILD_DIR/$APP_ROOT/ios
|
|
132
|
-
bundle exec fastlane upload_metadata_ios
|
|
133
|
-
|
|
134
|
-
- name: Sync IAP and subscriptions
|
|
135
|
-
script: |
|
|
136
|
-
set -euo pipefail
|
|
137
|
-
FORCE_FLAG=""
|
|
138
|
-
if [ ! -f "$CM_BUILD_DIR/.codemagic/ios_iap_synced" ]; then
|
|
139
|
-
FORCE_FLAG="--force"
|
|
140
|
-
echo "First IAP sync detected - forcing upload"
|
|
141
|
-
fi
|
|
142
|
-
if ./scripts/check_changed.sh $FORCE_FLAG fastlane/iap_config.json; then
|
|
143
|
-
cd $CM_BUILD_DIR/$APP_ROOT/ios
|
|
144
|
-
bundle exec fastlane sync_iap
|
|
145
|
-
# Create marker after successful sync
|
|
146
|
-
if [ -n "$FORCE_FLAG" ]; then
|
|
147
|
-
mkdir -p "$CM_BUILD_DIR/.codemagic"
|
|
148
|
-
touch "$CM_BUILD_DIR/.codemagic/ios_iap_synced"
|
|
149
|
-
cd "$CM_BUILD_DIR"
|
|
150
|
-
git add .codemagic/ios_iap_synced
|
|
151
|
-
git commit -m "chore: mark iOS IAP sync complete [skip ci]" || true
|
|
152
|
-
git push origin HEAD || true
|
|
153
|
-
echo "iOS IAP sync marker committed"
|
|
154
|
-
fi
|
|
155
|
-
else
|
|
156
|
-
echo "IAP config unchanged - skipping"
|
|
157
|
-
fi
|
|
158
|
-
|
|
159
|
-
- name: Set up iOS code signing
|
|
160
|
-
script: |
|
|
161
|
-
set -euo pipefail
|
|
162
|
-
keychain initialize
|
|
163
|
-
|
|
164
|
-
# Attempt to fetch or create signing files.
|
|
165
|
-
if app-store-connect fetch-signing-files "$BUNDLE_ID" \
|
|
166
|
-
--type IOS_APP_STORE \
|
|
167
|
-
--certificate-key=@env:CERTIFICATE_PRIVATE_KEY \
|
|
168
|
-
--create 2>/tmp/signing_err.log; then
|
|
169
|
-
echo "Signing files fetched successfully"
|
|
170
|
-
else
|
|
171
|
-
cat /tmp/signing_err.log
|
|
172
|
-
echo ""
|
|
173
|
-
echo "Signing failed. Deleting ALL distribution certs..."
|
|
174
|
-
|
|
175
|
-
for CERT_TYPE in IOS_DISTRIBUTION DISTRIBUTION; do
|
|
176
|
-
CERT_IDS=$(app-store-connect certificates list \
|
|
177
|
-
--type "$CERT_TYPE" --json 2>/dev/null \
|
|
178
|
-
| python3 -c "
|
|
179
|
-
import sys, json
|
|
180
|
-
certs = json.load(sys.stdin)
|
|
181
|
-
for c in certs:
|
|
182
|
-
print(c.get('id', ''))
|
|
183
|
-
" 2>/dev/null || true)
|
|
184
|
-
|
|
185
|
-
for CID in $CERT_IDS; do
|
|
186
|
-
if [ -n "$CID" ]; then
|
|
187
|
-
echo "Deleting $CERT_TYPE cert: $CID"
|
|
188
|
-
app-store-connect certificates delete "$CID" || true
|
|
189
|
-
fi
|
|
190
|
-
done
|
|
191
|
-
done
|
|
192
|
-
|
|
193
|
-
echo "Waiting 15s for Apple API propagation..."
|
|
194
|
-
sleep 15
|
|
195
|
-
|
|
196
|
-
# Verify certs are gone
|
|
197
|
-
REMAINING=$(app-store-connect certificates list \
|
|
198
|
-
--type DISTRIBUTION --json 2>/dev/null \
|
|
199
|
-
| python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "?")
|
|
200
|
-
echo "Remaining DISTRIBUTION certs: $REMAINING"
|
|
201
|
-
|
|
202
|
-
echo "Retrying fetch-signing-files..."
|
|
203
|
-
app-store-connect fetch-signing-files "$BUNDLE_ID" \
|
|
204
|
-
--type IOS_APP_STORE \
|
|
205
|
-
--certificate-key=@env:CERTIFICATE_PRIVATE_KEY \
|
|
206
|
-
--create
|
|
207
|
-
fi
|
|
208
|
-
|
|
209
|
-
# Verify signing artifacts exist
|
|
210
|
-
P12_COUNT=$(ls /Users/builder/Library/MobileDevice/Certificates/*.p12 2>/dev/null | wc -l)
|
|
211
|
-
if [ "$P12_COUNT" -eq 0 ]; then
|
|
212
|
-
echo "ERROR: No .p12 certificates found after signing setup" >&2
|
|
213
|
-
exit 1
|
|
214
|
-
fi
|
|
215
|
-
|
|
216
|
-
keychain add-certificates
|
|
217
|
-
xcode-project use-profiles --project $APP_ROOT/ios/Runner.xcodeproj
|
|
218
|
-
|
|
219
|
-
- name: Manage iOS version
|
|
220
|
-
script: |
|
|
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
|
-
")"
|
|
234
|
-
echo "APP_VERSION=$APP_VERSION" >> $CM_ENV
|
|
235
|
-
echo "APP_VERSION_ID=$APP_VERSION_ID" >> $CM_ENV
|
|
236
|
-
echo "APP_STATUS=$APP_STATUS" >> $CM_ENV
|
|
237
|
-
echo "iOS version: $APP_VERSION (state: $APP_STATUS)"
|
|
238
|
-
|
|
239
|
-
- name: Set Flutter version
|
|
240
|
-
script: |
|
|
241
|
-
set -euo pipefail
|
|
242
|
-
BUILD_NUMBER=$(($(app-store-connect get-latest-app-store-build-number "$BUNDLE_ID") + 1))
|
|
243
|
-
if [ -z "$APP_VERSION" ]; then
|
|
244
|
-
APP_VERSION="1.0.0"
|
|
245
|
-
fi
|
|
246
|
-
# Normalize to semver (1.0 -> 1.0.0, 1 -> 1.0.0)
|
|
247
|
-
PARTS=$(echo "$APP_VERSION" | tr '.' '\n' | wc -l | tr -d ' ')
|
|
248
|
-
if [ "$PARTS" -eq 1 ]; then APP_VERSION="${APP_VERSION}.0.0"; fi
|
|
249
|
-
if [ "$PARTS" -eq 2 ]; then APP_VERSION="${APP_VERSION}.0"; fi
|
|
250
|
-
sed -i '' "s/^version:.*/version: ${APP_VERSION}+${BUILD_NUMBER}/" $APP_ROOT/pubspec.yaml
|
|
251
|
-
echo "Building: $APP_VERSION+$BUILD_NUMBER"
|
|
252
|
-
|
|
253
|
-
- name: Flutter packages
|
|
254
|
-
script: |
|
|
255
|
-
set -euo pipefail
|
|
256
|
-
cd $CM_BUILD_DIR/$APP_ROOT && flutter pub get
|
|
257
|
-
|
|
258
|
-
- name: Build iOS
|
|
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
|
|
265
|
-
|
|
266
|
-
- name: Upload IPA to App Store
|
|
267
|
-
script: |
|
|
268
|
-
set -euo pipefail
|
|
269
|
-
cd $CM_BUILD_DIR/$APP_ROOT/ios
|
|
270
|
-
bundle exec fastlane upload_binary_ios
|
|
271
|
-
|
|
272
|
-
artifacts:
|
|
273
|
-
- ${APP_ROOT}/build/ios/ipa/*.ipa
|
|
274
|
-
- /tmp/xcodebuild_logs/*.log
|
|
275
|
-
|
|
276
|
-
android-release:
|
|
277
|
-
name: Android Release
|
|
278
|
-
max_build_duration: 60
|
|
279
|
-
instance_type: mac_mini_m4
|
|
280
|
-
environment:
|
|
281
|
-
flutter: stable
|
|
282
|
-
groups:
|
|
283
|
-
- google_play_credentials
|
|
284
|
-
- android_keystore
|
|
285
|
-
vars:
|
|
286
|
-
PACKAGE_NAME: "${PACKAGE_NAME}"
|
|
287
|
-
APP_NAME: "${APP_NAME}"
|
|
288
|
-
BUNDLE_ID: "${BUNDLE_ID}"
|
|
289
|
-
GOOGLE_SA_JSON_PATH: "${GOOGLE_SA_JSON_PATH}"
|
|
290
|
-
KEYSTORE_PASSWORD: "${KEYSTORE_PASSWORD}"
|
|
291
|
-
TRACK: "${TRACK}"
|
|
292
|
-
ROLLOUT_FRACTION: "${ROLLOUT_FRACTION}"
|
|
293
|
-
IN_APP_UPDATE_PRIORITY: "${IN_APP_UPDATE_PRIORITY}"
|
|
294
|
-
APPLE_KEY_ID: "${APPLE_KEY_ID}"
|
|
295
|
-
APPLE_ISSUER_ID: "${APPLE_ISSUER_ID}"
|
|
296
|
-
P8_KEY_PATH: "${P8_KEY_PATH}"
|
|
297
|
-
APP_ROOT: "${APP_ROOT}"
|
|
298
|
-
cache:
|
|
299
|
-
cache_paths:
|
|
300
|
-
- $HOME/.gem
|
|
301
|
-
- $HOME/.gradle/caches
|
|
302
|
-
- ${APP_ROOT}/android/vendor/bundle
|
|
303
|
-
triggering:
|
|
304
|
-
events:
|
|
305
|
-
- push
|
|
306
|
-
branch_patterns:
|
|
307
|
-
- pattern: main
|
|
308
|
-
include: true
|
|
309
|
-
scripts:
|
|
310
|
-
- name: Link Fastlane directories
|
|
311
|
-
script: |
|
|
312
|
-
set -euo pipefail
|
|
313
|
-
ln -sfn "$CM_BUILD_DIR/fastlane/android" "$CM_BUILD_DIR/$APP_ROOT/android/fastlane"
|
|
314
|
-
ln -sfn "$CM_BUILD_DIR/fastlane" "$CM_BUILD_DIR/$APP_ROOT/fastlane"
|
|
315
|
-
|
|
316
|
-
- name: Ensure Android upload keystore
|
|
317
|
-
script: |
|
|
318
|
-
set -euo pipefail
|
|
319
|
-
KEYSTORE_PATH="$CM_BUILD_DIR/$APP_ROOT/android/upload.keystore"
|
|
320
|
-
CREDS_PATH="$CM_BUILD_DIR/creds/android_upload.keystore"
|
|
321
|
-
GENERATED=false
|
|
322
|
-
if [ -f "$KEYSTORE_PATH" ]; then
|
|
323
|
-
echo "Using existing upload keystore from repo"
|
|
324
|
-
elif [ -f "$CREDS_PATH" ]; then
|
|
325
|
-
echo "Restoring upload keystore from creds/"
|
|
326
|
-
cp "$CREDS_PATH" "$KEYSTORE_PATH"
|
|
327
|
-
else
|
|
328
|
-
echo "Generating new upload keystore..."
|
|
329
|
-
keytool -genkey -v \
|
|
330
|
-
-keystore "$KEYSTORE_PATH" \
|
|
331
|
-
-storetype JKS \
|
|
332
|
-
-keyalg RSA \
|
|
333
|
-
-keysize 2048 \
|
|
334
|
-
-validity 10000 \
|
|
335
|
-
-alias upload \
|
|
336
|
-
-storepass "$KEYSTORE_PASSWORD" \
|
|
337
|
-
-keypass "$KEYSTORE_PASSWORD" \
|
|
338
|
-
-dname "CN=Upload Key, O=Developer, C=US"
|
|
339
|
-
GENERATED=true
|
|
340
|
-
fi
|
|
341
|
-
# Always ensure creds/ backup exists
|
|
342
|
-
mkdir -p "$CM_BUILD_DIR/creds"
|
|
343
|
-
if [ ! -f "$CREDS_PATH" ] || ! cmp -s "$KEYSTORE_PATH" "$CREDS_PATH"; then
|
|
344
|
-
cp "$KEYSTORE_PATH" "$CREDS_PATH"
|
|
345
|
-
echo "Keystore backed up to creds/android_upload.keystore"
|
|
346
|
-
fi
|
|
347
|
-
# Commit keystore files if newly generated
|
|
348
|
-
if [ "$GENERATED" = "true" ]; then
|
|
349
|
-
cd "$CM_BUILD_DIR"
|
|
350
|
-
git add --force $APP_ROOT/android/upload.keystore creds/android_upload.keystore
|
|
351
|
-
git commit -m "chore: add Android upload keystore [skip ci]"
|
|
352
|
-
git push origin HEAD
|
|
353
|
-
echo "Upload keystore generated and committed to repo"
|
|
354
|
-
fi
|
|
355
|
-
echo "CM_KEYSTORE_PATH=$KEYSTORE_PATH" >> $CM_ENV
|
|
356
|
-
echo "CM_KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> $CM_ENV
|
|
357
|
-
echo "CM_KEY_ALIAS=upload" >> $CM_ENV
|
|
358
|
-
echo "CM_KEY_PASSWORD=$KEYSTORE_PASSWORD" >> $CM_ENV
|
|
359
|
-
|
|
360
|
-
- name: Check Google Play readiness
|
|
361
|
-
script: |
|
|
362
|
-
set -euo pipefail
|
|
363
|
-
echo "Checking Google Play setup status..."
|
|
364
|
-
pip3 install --break-system-packages PyJWT cryptography requests
|
|
365
|
-
export SA_JSON="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
|
|
366
|
-
export PACKAGE_NAME="$PACKAGE_NAME"
|
|
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
|
-
")
|
|
372
|
-
if [ "$READY" != "true" ]; then
|
|
373
|
-
echo "GOOGLE_PLAY_READY=false" >> $CM_ENV
|
|
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
|
|
393
|
-
echo "Google Play not ready - see HOW_TO_GOOGLE_PLAY.md in artifacts"
|
|
394
|
-
else
|
|
395
|
-
echo "GOOGLE_PLAY_READY=true" >> $CM_ENV
|
|
396
|
-
# Detect first store sync: if marker file doesn't exist, force IAP/data safety upload
|
|
397
|
-
if [ ! -f "$CM_BUILD_DIR/.codemagic/android_store_synced" ]; then
|
|
398
|
-
echo "FIRST_STORE_SYNC=true" >> $CM_ENV
|
|
399
|
-
echo "First store sync detected - IAP and data safety will be force-uploaded"
|
|
400
|
-
else
|
|
401
|
-
echo "FIRST_STORE_SYNC=false" >> $CM_ENV
|
|
402
|
-
echo "Marker found - using change detection for IAP/data safety"
|
|
403
|
-
fi
|
|
404
|
-
echo "Google Play ready for automated publishing"
|
|
405
|
-
fi
|
|
406
|
-
|
|
407
|
-
- name: Install Fastlane
|
|
408
|
-
script: |
|
|
409
|
-
set -euo pipefail
|
|
410
|
-
cd $CM_BUILD_DIR/$APP_ROOT/android
|
|
411
|
-
gem install bundler
|
|
412
|
-
bundle install
|
|
413
|
-
|
|
414
|
-
- name: Upload Android metadata and screenshots
|
|
415
|
-
script: |
|
|
416
|
-
set -euo pipefail
|
|
417
|
-
if [ "$GOOGLE_PLAY_READY" != "true" ]; then
|
|
418
|
-
echo "Google Play not ready - skipping metadata upload."
|
|
419
|
-
exit 0
|
|
420
|
-
fi
|
|
421
|
-
export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
|
|
422
|
-
cd $CM_BUILD_DIR/$APP_ROOT/android
|
|
423
|
-
bundle exec fastlane upload_metadata_android
|
|
424
|
-
echo "Metadata uploaded successfully"
|
|
425
|
-
|
|
426
|
-
- name: Sync subscriptions and IAP
|
|
427
|
-
script: |
|
|
428
|
-
set -euo pipefail
|
|
429
|
-
if [ "$GOOGLE_PLAY_READY" != "true" ]; then
|
|
430
|
-
echo "Google Play not ready - skipping IAP sync."
|
|
431
|
-
exit 0
|
|
432
|
-
fi
|
|
433
|
-
FORCE_FLAG=""
|
|
434
|
-
if [ "${FIRST_STORE_SYNC:-false}" = "true" ]; then
|
|
435
|
-
FORCE_FLAG="--force"
|
|
436
|
-
fi
|
|
437
|
-
if ./scripts/check_changed.sh $FORCE_FLAG fastlane/iap_config.json; then
|
|
438
|
-
export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
|
|
439
|
-
cd $CM_BUILD_DIR/$APP_ROOT/android
|
|
440
|
-
bundle exec fastlane sync_google_iap
|
|
441
|
-
echo "IAP sync completed successfully"
|
|
442
|
-
else
|
|
443
|
-
echo "IAP config unchanged - skipping"
|
|
444
|
-
fi
|
|
445
|
-
|
|
446
|
-
- name: Update data safety form
|
|
447
|
-
script: |
|
|
448
|
-
set -euo pipefail
|
|
449
|
-
if [ "$GOOGLE_PLAY_READY" != "true" ]; then
|
|
450
|
-
echo "Google Play not ready - skipping data safety update."
|
|
451
|
-
exit 0
|
|
452
|
-
fi
|
|
453
|
-
FORCE_FLAG=""
|
|
454
|
-
if [ "${FIRST_STORE_SYNC:-false}" = "true" ]; then
|
|
455
|
-
FORCE_FLAG="--force"
|
|
456
|
-
fi
|
|
457
|
-
if ./scripts/check_changed.sh $FORCE_FLAG fastlane/data_safety.csv; then
|
|
458
|
-
export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
|
|
459
|
-
cd $CM_BUILD_DIR/$APP_ROOT/android
|
|
460
|
-
bundle exec fastlane update_data_safety
|
|
461
|
-
echo "Data safety form updated successfully"
|
|
462
|
-
else
|
|
463
|
-
echo "Data safety unchanged - skipping"
|
|
464
|
-
fi
|
|
465
|
-
# After successful sync, create marker so future builds use change detection
|
|
466
|
-
if [ "${FIRST_STORE_SYNC:-false}" = "true" ]; then
|
|
467
|
-
mkdir -p "$CM_BUILD_DIR/.codemagic"
|
|
468
|
-
touch "$CM_BUILD_DIR/.codemagic/android_store_synced"
|
|
469
|
-
cd "$CM_BUILD_DIR"
|
|
470
|
-
git add .codemagic/android_store_synced
|
|
471
|
-
git commit -m "chore: mark Android store sync complete [skip ci]" || true
|
|
472
|
-
git push origin HEAD || true
|
|
473
|
-
echo "Store sync marker committed"
|
|
474
|
-
fi
|
|
475
|
-
|
|
476
|
-
- name: Manage Android version
|
|
477
|
-
script: |
|
|
478
|
-
set -euo pipefail
|
|
479
|
-
# Read iOS version for consistency (if iOS workflow ran)
|
|
480
|
-
pip3 install --break-system-packages PyJWT cryptography requests
|
|
481
|
-
echo "APP_STORE_CONNECT_KEY_IDENTIFIER=$APPLE_KEY_ID" >> $CM_ENV
|
|
482
|
-
echo "APP_STORE_CONNECT_ISSUER_ID=$APPLE_ISSUER_ID" >> $CM_ENV
|
|
483
|
-
echo "APP_STORE_CONNECT_PRIVATE_KEY<<KEYDELIMITER" >> $CM_ENV
|
|
484
|
-
cat "$CM_BUILD_DIR/$P8_KEY_PATH" >> $CM_ENV
|
|
485
|
-
echo "" >> $CM_ENV
|
|
486
|
-
echo "KEYDELIMITER" >> $CM_ENV
|
|
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
|
-
")"
|
|
494
|
-
echo "APP_VERSION=$APP_VERSION" >> $CM_ENV
|
|
495
|
-
if [ "$GOOGLE_PLAY_READY" != "true" ]; then
|
|
496
|
-
LATEST_BUILD=0
|
|
497
|
-
else
|
|
498
|
-
export GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS="$(cat $CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH)"
|
|
499
|
-
LATEST_BUILD_OUTPUT=$(google-play get-latest-build-number \
|
|
500
|
-
--package-name "$PACKAGE_NAME" \
|
|
501
|
-
--tracks=production,beta,alpha,internal)
|
|
502
|
-
# Extract just the number from output
|
|
503
|
-
LATEST_BUILD=$(echo "$LATEST_BUILD_OUTPUT" | grep -oE '^[0-9]+$' | tail -1)
|
|
504
|
-
if [ -z "$LATEST_BUILD" ]; then
|
|
505
|
-
echo "ERROR: Could not get latest build number from Google Play." >&2
|
|
506
|
-
echo "Raw output: $LATEST_BUILD_OUTPUT" >&2
|
|
507
|
-
exit 1
|
|
508
|
-
fi
|
|
509
|
-
echo "Latest build number from Google Play: $LATEST_BUILD"
|
|
510
|
-
fi
|
|
511
|
-
NEW_BUILD=$(($LATEST_BUILD + 1))
|
|
512
|
-
if [ -z "$APP_VERSION" ]; then
|
|
513
|
-
APP_VERSION="1.0.0"
|
|
514
|
-
fi
|
|
515
|
-
# Normalize to semver (1.0 -> 1.0.0, 1 -> 1.0.0)
|
|
516
|
-
PARTS=$(echo "$APP_VERSION" | tr '.' '\n' | wc -l | tr -d ' ')
|
|
517
|
-
if [ "$PARTS" -eq 1 ]; then APP_VERSION="${APP_VERSION}.0.0"; fi
|
|
518
|
-
if [ "$PARTS" -eq 2 ]; then APP_VERSION="${APP_VERSION}.0"; fi
|
|
519
|
-
sed -i '' "s/^version:.*/version: ${APP_VERSION}+${NEW_BUILD}/" $APP_ROOT/pubspec.yaml
|
|
520
|
-
echo "ANDROID_VERSION_CODE=$NEW_BUILD" >> $CM_ENV
|
|
521
|
-
echo "Android versionCode: $NEW_BUILD, versionName: $APP_VERSION"
|
|
522
|
-
|
|
523
|
-
- name: Flutter packages
|
|
524
|
-
script: |
|
|
525
|
-
set -euo pipefail
|
|
526
|
-
cd $CM_BUILD_DIR/$APP_ROOT && flutter pub get
|
|
527
|
-
|
|
528
|
-
- name: Build Android
|
|
529
|
-
script: |
|
|
530
|
-
set -euo pipefail
|
|
531
|
-
cd $CM_BUILD_DIR/$APP_ROOT && flutter build appbundle --release
|
|
532
|
-
|
|
533
|
-
- name: Upload AAB to Google Play
|
|
534
|
-
script: |
|
|
535
|
-
set -euo pipefail
|
|
536
|
-
if [ "$GOOGLE_PLAY_READY" != "true" ]; then
|
|
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
|
|
543
|
-
exit 1
|
|
544
|
-
fi
|
|
545
|
-
export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$CM_BUILD_DIR/$GOOGLE_SA_JSON_PATH"
|
|
546
|
-
cd $CM_BUILD_DIR/$APP_ROOT/android
|
|
547
|
-
bundle exec fastlane upload_binary_android
|
|
548
|
-
|
|
549
|
-
artifacts:
|
|
550
|
-
- ${APP_ROOT}/build/app/outputs/**/*.aab
|
|
551
|
-
- $CM_BUILD_DIR/HOW_TO_GOOGLE_PLAY.md
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
name: Trigger Codemagic Builds
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
|
|
7
|
-
# Cancel in-progress runs when a newer build is triggered
|
|
8
|
-
concurrency:
|
|
9
|
-
group: codemagic-trigger-${{ github.ref }}
|
|
10
|
-
cancel-in-progress: true
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
trigger:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- name: Checkout config
|
|
17
|
-
uses: actions/checkout@v4
|
|
18
|
-
with:
|
|
19
|
-
sparse-checkout: ci.config.yaml
|
|
20
|
-
sparse-checkout-cone-mode: false
|
|
21
|
-
|
|
22
|
-
- name: Install yq
|
|
23
|
-
run: |
|
|
24
|
-
if ! command -v yq &> /dev/null; then
|
|
25
|
-
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
|
|
26
|
-
sudo chmod +x /usr/local/bin/yq
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
- name: Read Codemagic config
|
|
30
|
-
id: config
|
|
31
|
-
run: |
|
|
32
|
-
if [ ! -f ci.config.yaml ]; then
|
|
33
|
-
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
34
|
-
echo "ci.config.yaml not found - skipping trigger."
|
|
35
|
-
exit 0
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
APP_ID=$(yq -r '.codemagic.app_id // ""' ci.config.yaml)
|
|
39
|
-
if [ -z "$APP_ID" ] || [ "$APP_ID" = "null" ]; then
|
|
40
|
-
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
41
|
-
echo "app_id not configured - skipping trigger."
|
|
42
|
-
exit 0
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
echo "app_id=$APP_ID" >> "$GITHUB_OUTPUT"
|
|
46
|
-
|
|
47
|
-
WORKFLOWS=$(yq -r '.codemagic.workflows[]' ci.config.yaml)
|
|
48
|
-
DELIMITER="EOF_$(date +%s%N)"
|
|
49
|
-
echo "workflows<<$DELIMITER" >> "$GITHUB_OUTPUT"
|
|
50
|
-
echo "$WORKFLOWS" >> "$GITHUB_OUTPUT"
|
|
51
|
-
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
|
52
|
-
|
|
53
|
-
- name: Trigger Codemagic builds
|
|
54
|
-
if: steps.config.outputs.skip != 'true'
|
|
55
|
-
env:
|
|
56
|
-
CM_TOKEN: ${{ secrets.CM_API_TOKEN }}
|
|
57
|
-
APP_ID: ${{ steps.config.outputs.app_id }}
|
|
58
|
-
BRANCH: ${{ github.ref_name }}
|
|
59
|
-
run: |
|
|
60
|
-
FAILED=0
|
|
61
|
-
while IFS= read -r workflow; do
|
|
62
|
-
[ -z "$workflow" ] && continue
|
|
63
|
-
if ! echo "$workflow" | grep -Eq '^[a-zA-Z0-9_-]+$'; then
|
|
64
|
-
echo "ERROR: Invalid workflow name: $workflow" >&2
|
|
65
|
-
exit 1
|
|
66
|
-
fi
|
|
67
|
-
echo "Triggering: $workflow"
|
|
68
|
-
curl --max-time 30 --retry 2 -sf -X POST https://api.codemagic.io/builds \
|
|
69
|
-
-H "Content-Type: application/json" \
|
|
70
|
-
-H "x-auth-token: $CM_TOKEN" \
|
|
71
|
-
-d "{\"appId\": \"$APP_ID\", \"workflowId\": \"$workflow\", \"branch\": \"$BRANCH\"}" \
|
|
72
|
-
&& echo " -> success" \
|
|
73
|
-
|| { echo " -> FAILED"; FAILED=1; }
|
|
74
|
-
done <<< "${{ steps.config.outputs.workflows }}"
|
|
75
|
-
if [ "$FAILED" -ne 0 ]; then
|
|
76
|
-
echo "ERROR: One or more Codemagic workflow triggers failed" >&2
|
|
77
|
-
exit 1
|
|
78
|
-
fi
|