@daemux/store-automator 0.7.1 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/bin/cli.mjs +38 -14
- package/package.json +1 -1
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/plugins/store-automator/agents/appstore-meta-creator.md +13 -8
- package/plugins/store-automator/agents/architect.md +42 -1
- package/plugins/store-automator/agents/devops.md +26 -27
- package/scripts/check_google_play.py +1 -1
- package/scripts/manage_version_ios.py +1 -1
- package/src/ci-config.mjs +21 -0
- package/src/install-paths.mjs +92 -0
- package/src/install.mjs +33 -78
- package/src/templates.mjs +71 -1
- package/templates/CLAUDE.md.template +70 -16
- package/templates/Matchfile.template +8 -0
- package/templates/ci.config.yaml.template +3 -0
- package/templates/codemagic.template.yaml +131 -96
- package/templates/fastlane/android/Fastfile.template +23 -5
- package/templates/fastlane/ios/Fastfile.template +4 -3
- package/templates/github/workflows/android-release.yml +141 -0
- package/templates/github/workflows/codemagic-trigger.yml +15 -5
- package/templates/github/workflows/ios-release.yml +119 -0
- package/templates/scripts/check_google_play.py +1 -1
- package/templates/scripts/ci/android/build.sh +50 -0
- package/templates/scripts/ci/android/check-readiness.sh +93 -0
- package/templates/scripts/ci/android/manage-version.sh +134 -0
- package/templates/scripts/ci/android/setup-keystore.sh +80 -0
- package/templates/scripts/ci/android/sync-iap.sh +63 -0
- package/templates/scripts/ci/android/update-data-safety.sh +65 -0
- package/templates/scripts/ci/android/upload-binary.sh +62 -0
- package/templates/scripts/ci/android/upload-metadata.sh +77 -0
- package/templates/scripts/ci/common/check-changed.sh +74 -0
- package/templates/scripts/ci/common/flutter-setup.sh +22 -0
- package/templates/scripts/ci/common/install-fastlane.sh +39 -0
- package/templates/scripts/ci/common/link-fastlane.sh +56 -0
- package/templates/scripts/ci/common/read-config.sh +74 -0
- package/templates/scripts/ci/ios/build.sh +91 -0
- package/templates/scripts/ci/ios/manage-version.sh +64 -0
- package/templates/scripts/ci/ios/set-build-number.sh +92 -0
- package/templates/scripts/ci/ios/setup-signing.sh +283 -0
- package/templates/scripts/ci/ios/sync-iap.sh +80 -0
- package/templates/scripts/ci/ios/upload-binary.sh +43 -0
- package/templates/scripts/ci/ios/upload-metadata.sh +82 -0
- package/templates/scripts/create_app_record.py +1 -1
- package/templates/scripts/manage_version_ios.py +1 -1
- package/templates/scripts/update_data_safety.py +220 -0
|
@@ -4,6 +4,11 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
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
|
+
|
|
7
12
|
jobs:
|
|
8
13
|
trigger:
|
|
9
14
|
runs-on: ubuntu-latest
|
|
@@ -52,11 +57,12 @@ jobs:
|
|
|
52
57
|
APP_ID: ${{ steps.config.outputs.app_id }}
|
|
53
58
|
BRANCH: ${{ github.ref_name }}
|
|
54
59
|
run: |
|
|
55
|
-
|
|
60
|
+
FAILED=0
|
|
61
|
+
while IFS= read -r workflow; do
|
|
56
62
|
[ -z "$workflow" ] && continue
|
|
57
63
|
if ! echo "$workflow" | grep -Eq '^[a-zA-Z0-9_-]+$'; then
|
|
58
|
-
echo "ERROR: Invalid workflow name: $workflow"
|
|
59
|
-
|
|
64
|
+
echo "ERROR: Invalid workflow name: $workflow" >&2
|
|
65
|
+
exit 1
|
|
60
66
|
fi
|
|
61
67
|
echo "Triggering: $workflow"
|
|
62
68
|
curl --max-time 30 --retry 2 -sf -X POST https://api.codemagic.io/builds \
|
|
@@ -64,5 +70,9 @@ jobs:
|
|
|
64
70
|
-H "x-auth-token: $CM_TOKEN" \
|
|
65
71
|
-d "{\"appId\": \"$APP_ID\", \"workflowId\": \"$workflow\", \"branch\": \"$BRANCH\"}" \
|
|
66
72
|
&& echo " -> success" \
|
|
67
|
-
|| echo " ->
|
|
68
|
-
done
|
|
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
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
name: iOS Release
|
|
2
|
+
# POST-MIGRATION CLEANUP (after GitHub Actions workflows are verified in production):
|
|
3
|
+
# - Remove .github/workflows/codemagic-trigger.yml
|
|
4
|
+
# - Remove any Codemagic-specific configuration files
|
|
5
|
+
# - Update README/docs to reference GitHub Actions instead of Codemagic
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
# Cancel in-progress runs when a newer build is triggered
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ios-release-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
metadata:
|
|
18
|
+
name: Upload iOS Metadata
|
|
19
|
+
runs-on: macos-latest
|
|
20
|
+
timeout-minutes: 30
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
|
|
27
|
+
- name: Setup Ruby
|
|
28
|
+
uses: ruby/setup-ruby@v1
|
|
29
|
+
with:
|
|
30
|
+
ruby-version: '3.3'
|
|
31
|
+
|
|
32
|
+
- name: Cache .ci-state
|
|
33
|
+
uses: actions/cache@v4
|
|
34
|
+
with:
|
|
35
|
+
path: .ci-state
|
|
36
|
+
key: ci-state-ios-metadata-${{ hashFiles('fastlane/metadata/**', 'fastlane/screenshots/ios/**') }}
|
|
37
|
+
restore-keys: ci-state-ios-metadata-
|
|
38
|
+
|
|
39
|
+
- name: Install yq
|
|
40
|
+
run: brew install yq
|
|
41
|
+
|
|
42
|
+
- name: Link Fastlane
|
|
43
|
+
run: scripts/ci/common/link-fastlane.sh ios
|
|
44
|
+
|
|
45
|
+
- name: Install Fastlane
|
|
46
|
+
run: scripts/ci/common/install-fastlane.sh ios
|
|
47
|
+
|
|
48
|
+
- name: Upload Metadata & Screenshots
|
|
49
|
+
run: scripts/ci/ios/upload-metadata.sh
|
|
50
|
+
|
|
51
|
+
- name: Sync IAP
|
|
52
|
+
run: scripts/ci/ios/sync-iap.sh
|
|
53
|
+
|
|
54
|
+
- name: Save .ci-state
|
|
55
|
+
if: success()
|
|
56
|
+
uses: actions/cache/save@v4
|
|
57
|
+
with:
|
|
58
|
+
path: .ci-state
|
|
59
|
+
key: ci-state-ios-metadata-${{ hashFiles('fastlane/metadata/**', 'fastlane/screenshots/ios/**') }}
|
|
60
|
+
|
|
61
|
+
build:
|
|
62
|
+
name: Build & Upload iOS
|
|
63
|
+
needs: [metadata]
|
|
64
|
+
runs-on: macos-latest
|
|
65
|
+
timeout-minutes: 60
|
|
66
|
+
env:
|
|
67
|
+
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
|
68
|
+
steps:
|
|
69
|
+
- name: Checkout
|
|
70
|
+
uses: actions/checkout@v4
|
|
71
|
+
with:
|
|
72
|
+
fetch-depth: 0
|
|
73
|
+
|
|
74
|
+
- name: Setup Ruby
|
|
75
|
+
uses: ruby/setup-ruby@v1
|
|
76
|
+
with:
|
|
77
|
+
ruby-version: '3.3'
|
|
78
|
+
|
|
79
|
+
- name: Setup Flutter
|
|
80
|
+
uses: subosito/flutter-action@v2
|
|
81
|
+
with:
|
|
82
|
+
channel: stable
|
|
83
|
+
|
|
84
|
+
- name: Install yq
|
|
85
|
+
run: brew install yq
|
|
86
|
+
|
|
87
|
+
- name: Link Fastlane
|
|
88
|
+
run: scripts/ci/common/link-fastlane.sh ios
|
|
89
|
+
|
|
90
|
+
- name: Install Fastlane
|
|
91
|
+
run: scripts/ci/common/install-fastlane.sh ios
|
|
92
|
+
|
|
93
|
+
- name: Setup Signing (Match)
|
|
94
|
+
run: scripts/ci/ios/setup-signing.sh
|
|
95
|
+
|
|
96
|
+
- name: Install Python dependencies
|
|
97
|
+
run: pip3 install --break-system-packages PyJWT
|
|
98
|
+
|
|
99
|
+
- name: Manage Version
|
|
100
|
+
run: scripts/ci/ios/manage-version.sh
|
|
101
|
+
|
|
102
|
+
- name: Set Build Number
|
|
103
|
+
run: scripts/ci/ios/set-build-number.sh
|
|
104
|
+
|
|
105
|
+
- name: Flutter Packages
|
|
106
|
+
run: scripts/ci/common/flutter-setup.sh
|
|
107
|
+
|
|
108
|
+
- name: Build IPA
|
|
109
|
+
run: scripts/ci/ios/build.sh
|
|
110
|
+
|
|
111
|
+
- name: Upload Artifacts
|
|
112
|
+
if: always()
|
|
113
|
+
uses: actions/upload-artifact@v4
|
|
114
|
+
with:
|
|
115
|
+
name: ios-ipa
|
|
116
|
+
path: app/build/ios/ipa/*.ipa
|
|
117
|
+
|
|
118
|
+
- name: Upload to App Store
|
|
119
|
+
run: scripts/ci/ios/upload-binary.sh
|
|
@@ -25,7 +25,7 @@ except ImportError:
|
|
|
25
25
|
print("Installing dependencies...", file=sys.stderr)
|
|
26
26
|
import subprocess
|
|
27
27
|
subprocess.check_call(
|
|
28
|
-
[sys.executable, "-m", "pip", "install", "PyJWT", "cryptography", "requests"],
|
|
28
|
+
[sys.executable, "-m", "pip", "install", "--break-system-packages", "PyJWT", "cryptography", "requests"],
|
|
29
29
|
stdout=subprocess.DEVNULL,
|
|
30
30
|
)
|
|
31
31
|
import jwt
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
6
|
+
|
|
7
|
+
# --- Validate prerequisites ---
|
|
8
|
+
if [ -z "${CM_KEYSTORE_PATH:-}" ]; then
|
|
9
|
+
echo "ERROR: CM_KEYSTORE_PATH not set. Run setup-keystore.sh first." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
if [ ! -f "$CM_KEYSTORE_PATH" ]; then
|
|
13
|
+
echo "ERROR: Keystore not found at $CM_KEYSTORE_PATH" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
if ! command -v flutter &>/dev/null; then
|
|
17
|
+
echo "ERROR: Flutter SDK not found in PATH" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Building Android AAB..."
|
|
22
|
+
echo " APP_ROOT: $APP_ROOT"
|
|
23
|
+
echo " CM_KEYSTORE_PATH: $CM_KEYSTORE_PATH"
|
|
24
|
+
echo " CM_KEY_ALIAS: ${CM_KEY_ALIAS:-not set}"
|
|
25
|
+
|
|
26
|
+
# --- Build AAB ---
|
|
27
|
+
cd "$APP_ROOT"
|
|
28
|
+
flutter build appbundle --release
|
|
29
|
+
|
|
30
|
+
# --- Locate AAB ---
|
|
31
|
+
AAB_DIR="$APP_ROOT/build/app/outputs/bundle/release"
|
|
32
|
+
AAB_FILE=$(find "$AAB_DIR" -name "*.aab" -type f 2>/dev/null | head -1)
|
|
33
|
+
|
|
34
|
+
if [ -z "${AAB_FILE:-}" ]; then
|
|
35
|
+
echo "ERROR: No .aab file found in $AAB_DIR" >&2
|
|
36
|
+
ls -la "$AAB_DIR" >&2 2>/dev/null || true
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "AAB built: $AAB_FILE ($(du -h "$AAB_FILE" | cut -f1))"
|
|
41
|
+
|
|
42
|
+
# --- Export ---
|
|
43
|
+
export AAB_PATH="$AAB_FILE"
|
|
44
|
+
|
|
45
|
+
if [ -n "${GITHUB_ENV:-}" ]; then
|
|
46
|
+
echo "AAB_PATH=$AAB_FILE" >> "$GITHUB_ENV"
|
|
47
|
+
echo "Exported AAB_PATH to GITHUB_ENV"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
echo "Android build complete: $AAB_PATH"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
6
|
+
|
|
7
|
+
# --- Helpers ---
|
|
8
|
+
|
|
9
|
+
# Export GOOGLE_PLAY_READY and persist to $GITHUB_ENV when in CI.
|
|
10
|
+
set_play_ready() {
|
|
11
|
+
local value="$1"
|
|
12
|
+
export GOOGLE_PLAY_READY="$value"
|
|
13
|
+
if [ -n "${GITHUB_ENV:-}" ]; then
|
|
14
|
+
echo "GOOGLE_PLAY_READY=$value" >> "$GITHUB_ENV"
|
|
15
|
+
fi
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Log an error, mark not-ready, and hard-fail.
|
|
19
|
+
fail_not_ready() {
|
|
20
|
+
echo "ERROR: $1" >&2
|
|
21
|
+
set_play_ready "false"
|
|
22
|
+
exit 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# --- Validate required config ---
|
|
26
|
+
if [ -z "${GOOGLE_SA_JSON_PATH:-}" ]; then
|
|
27
|
+
fail_not_ready "GOOGLE_SA_JSON_PATH not set in ci.config.yaml"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
SA_FULL_PATH="$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH"
|
|
31
|
+
if [ ! -f "$SA_FULL_PATH" ]; then
|
|
32
|
+
fail_not_ready "Service account JSON not found at $SA_FULL_PATH"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [ -z "${PACKAGE_NAME:-}" ]; then
|
|
36
|
+
fail_not_ready "PACKAGE_NAME not set in ci.config.yaml"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# --- Set env vars for the Python script ---
|
|
40
|
+
export SA_JSON="$SA_FULL_PATH"
|
|
41
|
+
export PACKAGE_NAME
|
|
42
|
+
|
|
43
|
+
# --- Run the existing Python script ---
|
|
44
|
+
echo "Checking Google Play readiness for $PACKAGE_NAME..."
|
|
45
|
+
READINESS_JSON=$(python3 "$PROJECT_ROOT/scripts/check_google_play.py")
|
|
46
|
+
|
|
47
|
+
if [ -z "$READINESS_JSON" ]; then
|
|
48
|
+
fail_not_ready "check_google_play.py returned empty output"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
echo "Readiness info: $READINESS_JSON"
|
|
52
|
+
|
|
53
|
+
# --- Parse JSON output (single invocation for both fields) ---
|
|
54
|
+
# Expected format: {"ready": true/false, "missing_steps": [...]}
|
|
55
|
+
PARSED=$(echo "$READINESS_JSON" | python3 -c "
|
|
56
|
+
import sys, json
|
|
57
|
+
data = json.load(sys.stdin)
|
|
58
|
+
ready = str(data.get('ready', False)).lower()
|
|
59
|
+
steps = data.get('missing_steps', [])
|
|
60
|
+
print(ready)
|
|
61
|
+
for s in steps:
|
|
62
|
+
print(f' - {s}')
|
|
63
|
+
")
|
|
64
|
+
|
|
65
|
+
# First line is the ready status; remaining lines are missing steps.
|
|
66
|
+
READY=$(echo "$PARSED" | head -n 1)
|
|
67
|
+
MISSING_STEPS=$(echo "$PARSED" | tail -n +2)
|
|
68
|
+
|
|
69
|
+
# --- Export result ---
|
|
70
|
+
set_play_ready "$READY"
|
|
71
|
+
|
|
72
|
+
# --- Print guidance if not ready ---
|
|
73
|
+
if [ "$READY" != "true" ]; then
|
|
74
|
+
echo ""
|
|
75
|
+
echo "============================================="
|
|
76
|
+
echo " Google Play is NOT ready for automation"
|
|
77
|
+
echo "============================================="
|
|
78
|
+
echo ""
|
|
79
|
+
echo "Missing steps:"
|
|
80
|
+
echo "$MISSING_STEPS"
|
|
81
|
+
echo ""
|
|
82
|
+
echo "To fix:"
|
|
83
|
+
echo " 1. Go to Google Play Console: https://play.google.com/console"
|
|
84
|
+
echo " 2. Ensure the app ($PACKAGE_NAME) has been manually created"
|
|
85
|
+
echo " 3. Complete the store listing, content rating, and pricing"
|
|
86
|
+
echo " 4. Upload at least one AAB manually for the first release"
|
|
87
|
+
echo " 5. Grant the service account access to the app"
|
|
88
|
+
echo ""
|
|
89
|
+
echo "Google Play is NOT ready. CI cannot proceed."
|
|
90
|
+
exit 1
|
|
91
|
+
else
|
|
92
|
+
echo "Google Play is ready for automated publishing"
|
|
93
|
+
fi
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
6
|
+
|
|
7
|
+
PUBSPEC="$APP_ROOT/pubspec.yaml"
|
|
8
|
+
|
|
9
|
+
# --- Step 1: Get iOS version (for cross-platform consistency) ---
|
|
10
|
+
|
|
11
|
+
echo "Getting iOS version for cross-platform consistency..."
|
|
12
|
+
|
|
13
|
+
P8_FULL_PATH="$PROJECT_ROOT/$P8_KEY_PATH"
|
|
14
|
+
if [ ! -f "$P8_FULL_PATH" ]; then
|
|
15
|
+
echo "ERROR: P8 key file not found at $P8_FULL_PATH" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
export APP_STORE_CONNECT_KEY_IDENTIFIER="$APPLE_KEY_ID"
|
|
20
|
+
export APP_STORE_CONNECT_ISSUER_ID="$APPLE_ISSUER_ID"
|
|
21
|
+
export APP_STORE_CONNECT_PRIVATE_KEY
|
|
22
|
+
APP_STORE_CONNECT_PRIVATE_KEY=$(cat "$P8_FULL_PATH")
|
|
23
|
+
|
|
24
|
+
VERSION_JSON=$(python3 "$PROJECT_ROOT/scripts/manage_version_ios.py")
|
|
25
|
+
if [ -z "$VERSION_JSON" ]; then
|
|
26
|
+
echo "ERROR: manage_version_ios.py returned empty output" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
APP_VERSION=$(echo "$VERSION_JSON" \
|
|
31
|
+
| python3 -c "import sys,json; print(json.load(sys.stdin)['version'])")
|
|
32
|
+
echo "iOS version: $APP_VERSION"
|
|
33
|
+
|
|
34
|
+
# --- Step 2: Get latest Android version code ---
|
|
35
|
+
|
|
36
|
+
echo "Getting latest Android version code..."
|
|
37
|
+
|
|
38
|
+
LATEST_CODE="0"
|
|
39
|
+
|
|
40
|
+
if [ "${GOOGLE_PLAY_READY:-false}" != "true" ]; then
|
|
41
|
+
echo "Google Play not ready. Reading version code from pubspec.yaml or default."
|
|
42
|
+
if [ -f "$PUBSPEC" ]; then
|
|
43
|
+
CURRENT_VERSION=$(grep '^version:' "$PUBSPEC" | head -1 | sed 's/version: //')
|
|
44
|
+
if [[ "$CURRENT_VERSION" == *"+"* ]]; then
|
|
45
|
+
LATEST_CODE="${CURRENT_VERSION#*+}"
|
|
46
|
+
echo "Current version code from pubspec.yaml: $LATEST_CODE"
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
elif [ ! -f "$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH" ]; then
|
|
50
|
+
echo "ERROR: Service account JSON not found at $PROJECT_ROOT/$GOOGLE_SA_JSON_PATH" >&2
|
|
51
|
+
exit 1
|
|
52
|
+
else
|
|
53
|
+
SA_FULL_PATH="$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH"
|
|
54
|
+
cd "$APP_ROOT/android"
|
|
55
|
+
|
|
56
|
+
if ! bundle exec fastlane --version >/dev/null 2>&1; then
|
|
57
|
+
"$SCRIPT_DIR/../common/install-fastlane.sh" android
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
export PACKAGE_NAME
|
|
61
|
+
export GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH"
|
|
62
|
+
export TRACK="${TRACK:-internal}"
|
|
63
|
+
|
|
64
|
+
# Primary: Fastlane action
|
|
65
|
+
LATEST_CODE=$(bundle exec fastlane run google_play_track_version_codes \
|
|
66
|
+
package_name:"$PACKAGE_NAME" \
|
|
67
|
+
json_key:"$SA_FULL_PATH" \
|
|
68
|
+
track:"$TRACK" 2>&1 \
|
|
69
|
+
| grep -oE '[0-9]+' | sort -n | tail -1)
|
|
70
|
+
|
|
71
|
+
# Fallback: direct Ruby Supply::Client API
|
|
72
|
+
if [ -z "$LATEST_CODE" ] || [ "$LATEST_CODE" = "0" ]; then
|
|
73
|
+
LATEST_CODE=$(bundle exec ruby -e "
|
|
74
|
+
require 'supply'
|
|
75
|
+
require 'supply/client'
|
|
76
|
+
|
|
77
|
+
Supply.config = FastlaneCore::Configuration.create(
|
|
78
|
+
Supply::Options.available_options,
|
|
79
|
+
{
|
|
80
|
+
package_name: ENV['PACKAGE_NAME'],
|
|
81
|
+
json_key: ENV['GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH'],
|
|
82
|
+
track: ENV.fetch('TRACK', 'internal')
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
client = Supply::Client.make_from_config
|
|
87
|
+
all_codes = []
|
|
88
|
+
['internal', 'alpha', 'beta', 'production'].each do |track|
|
|
89
|
+
begin
|
|
90
|
+
track_codes = client.track_version_codes(track)
|
|
91
|
+
all_codes.concat(track_codes) if track_codes
|
|
92
|
+
rescue => e
|
|
93
|
+
# Track may not exist yet
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
puts all_codes.max || 0
|
|
97
|
+
")
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if [ -z "$LATEST_CODE" ]; then
|
|
101
|
+
echo "ERROR: Failed to fetch version codes from Google Play" >&2
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo "Latest version code from Google Play: $LATEST_CODE"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# --- Step 3: Increment and write ---
|
|
109
|
+
|
|
110
|
+
BUILD_NUMBER=$((LATEST_CODE + 1))
|
|
111
|
+
ANDROID_VERSION_CODE="$BUILD_NUMBER"
|
|
112
|
+
|
|
113
|
+
echo "New Android version code: $BUILD_NUMBER"
|
|
114
|
+
|
|
115
|
+
if [ ! -f "$PUBSPEC" ]; then
|
|
116
|
+
echo "ERROR: pubspec.yaml not found at $PUBSPEC" >&2
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
sed -i '' "s/^version: .*/version: ${APP_VERSION}+${BUILD_NUMBER}/" "$PUBSPEC"
|
|
121
|
+
echo "Updated pubspec.yaml: $(grep '^version:' "$PUBSPEC" | head -1)"
|
|
122
|
+
|
|
123
|
+
# --- Step 4: Export ---
|
|
124
|
+
|
|
125
|
+
export APP_VERSION BUILD_NUMBER ANDROID_VERSION_CODE
|
|
126
|
+
|
|
127
|
+
if [ -n "${GITHUB_ENV:-}" ]; then
|
|
128
|
+
echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_ENV"
|
|
129
|
+
echo "BUILD_NUMBER=$BUILD_NUMBER" >> "$GITHUB_ENV"
|
|
130
|
+
echo "ANDROID_VERSION_CODE=$ANDROID_VERSION_CODE" >> "$GITHUB_ENV"
|
|
131
|
+
echo "Exported version vars to GITHUB_ENV"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
echo "Android version management complete: ${APP_VERSION}+${BUILD_NUMBER}"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
6
|
+
|
|
7
|
+
# --- Helper: write key=value to $GITHUB_ENV when running in CI ---
|
|
8
|
+
persist_to_github_env() {
|
|
9
|
+
if [ -n "${GITHUB_ENV:-}" ]; then
|
|
10
|
+
for pair in "$@"; do
|
|
11
|
+
echo "$pair" >> "$GITHUB_ENV"
|
|
12
|
+
done
|
|
13
|
+
fi
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# --- Require keystore password (needed for generation AND validation) ---
|
|
17
|
+
if [ -z "${KEYSTORE_PASSWORD:-}" ]; then
|
|
18
|
+
echo "ERROR: KEYSTORE_PASSWORD not set in ci.config.yaml (credentials.keystore_password)" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
ANDROID_DIR="$APP_ROOT/android"
|
|
23
|
+
KEYSTORE_TARGET="$ANDROID_DIR/upload.keystore"
|
|
24
|
+
CREDS_KEYSTORE="$PROJECT_ROOT/creds/android_upload.keystore"
|
|
25
|
+
|
|
26
|
+
# --- Locate or create keystore ---
|
|
27
|
+
if [ -f "$KEYSTORE_TARGET" ]; then
|
|
28
|
+
echo "Keystore found at $KEYSTORE_TARGET"
|
|
29
|
+
elif [ -f "$CREDS_KEYSTORE" ]; then
|
|
30
|
+
echo "Keystore found in creds/, copying to $KEYSTORE_TARGET"
|
|
31
|
+
cp "$CREDS_KEYSTORE" "$KEYSTORE_TARGET"
|
|
32
|
+
else
|
|
33
|
+
echo "No keystore found. Generating a new upload keystore..."
|
|
34
|
+
|
|
35
|
+
keytool -genkey -v \
|
|
36
|
+
-keystore "$KEYSTORE_TARGET" \
|
|
37
|
+
-keyalg RSA \
|
|
38
|
+
-keysize 2048 \
|
|
39
|
+
-validity 10000 \
|
|
40
|
+
-alias upload \
|
|
41
|
+
-storepass "$KEYSTORE_PASSWORD" \
|
|
42
|
+
-keypass "$KEYSTORE_PASSWORD" \
|
|
43
|
+
-dname "CN=Upload Key, O=${APP_NAME:-App}"
|
|
44
|
+
|
|
45
|
+
echo "New keystore generated at $KEYSTORE_TARGET"
|
|
46
|
+
|
|
47
|
+
# Backup to creds/ for local development persistence.
|
|
48
|
+
# In CI the workspace is ephemeral, so the keystore should already exist
|
|
49
|
+
# in creds/ (committed or restored from a secret/artifact).
|
|
50
|
+
mkdir -p "$PROJECT_ROOT/creds"
|
|
51
|
+
cp "$KEYSTORE_TARGET" "$CREDS_KEYSTORE"
|
|
52
|
+
echo "Backed up keystore to $CREDS_KEYSTORE"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# --- Resolve absolute path ---
|
|
56
|
+
KEYSTORE_ABS_PATH="$(cd "$(dirname "$KEYSTORE_TARGET")" && pwd)/$(basename "$KEYSTORE_TARGET")"
|
|
57
|
+
|
|
58
|
+
# --- Validate keystore ---
|
|
59
|
+
echo "Validating keystore..."
|
|
60
|
+
if ! keytool -list -keystore "$KEYSTORE_ABS_PATH" -storepass "$KEYSTORE_PASSWORD" -alias upload >/dev/null 2>&1; then
|
|
61
|
+
echo "ERROR: Keystore validation failed. Check password and alias." >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
echo "Keystore validated successfully"
|
|
65
|
+
|
|
66
|
+
# --- Set Gradle signing env vars (matches build.gradle.kts) ---
|
|
67
|
+
export CM_KEYSTORE_PATH="$KEYSTORE_ABS_PATH"
|
|
68
|
+
export CM_KEYSTORE_PASSWORD="$KEYSTORE_PASSWORD"
|
|
69
|
+
export CM_KEY_ALIAS="upload"
|
|
70
|
+
export CM_KEY_PASSWORD="$KEYSTORE_PASSWORD"
|
|
71
|
+
|
|
72
|
+
persist_to_github_env \
|
|
73
|
+
"CM_KEYSTORE_PATH=$CM_KEYSTORE_PATH" \
|
|
74
|
+
"CM_KEYSTORE_PASSWORD=$CM_KEYSTORE_PASSWORD" \
|
|
75
|
+
"CM_KEY_ALIAS=$CM_KEY_ALIAS" \
|
|
76
|
+
"CM_KEY_PASSWORD=$CM_KEY_PASSWORD"
|
|
77
|
+
|
|
78
|
+
echo "Android keystore setup complete"
|
|
79
|
+
echo " CM_KEYSTORE_PATH=$CM_KEYSTORE_PATH"
|
|
80
|
+
echo " CM_KEY_ALIAS=$CM_KEY_ALIAS"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
6
|
+
|
|
7
|
+
# --- Check Google Play readiness ---
|
|
8
|
+
if [ "${GOOGLE_PLAY_READY:-false}" != "true" ]; then
|
|
9
|
+
echo "ERROR: Google Play not ready. Cannot sync IAPs." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# --- Check if IAP config exists ---
|
|
14
|
+
IAP_CONFIG="$PROJECT_ROOT/fastlane/iap_config.json"
|
|
15
|
+
if [ ! -f "$IAP_CONFIG" ]; then
|
|
16
|
+
echo "No iap_config.json found at $IAP_CONFIG. Skipping IAP sync."
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# --- Check if IAP plugin is available ---
|
|
21
|
+
cd "$APP_ROOT/android"
|
|
22
|
+
if ! bundle exec gem list fastlane-plugin-iap --installed; then
|
|
23
|
+
echo "ERROR: fastlane-plugin-iap not installed." >&2
|
|
24
|
+
echo "To enable: add it to app/android/Pluginfile and run 'bundle install'." >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# --- Hash-based change detection ---
|
|
29
|
+
STATE_DIR="$PROJECT_ROOT/.ci-state"
|
|
30
|
+
mkdir -p "$STATE_DIR"
|
|
31
|
+
|
|
32
|
+
HASH=$(shasum -a 256 "$IAP_CONFIG" | cut -d' ' -f1)
|
|
33
|
+
STATE_FILE="$STATE_DIR/android-iap-hash"
|
|
34
|
+
|
|
35
|
+
if [ -f "$STATE_FILE" ]; then
|
|
36
|
+
STORED_HASH=$(cat "$STATE_FILE")
|
|
37
|
+
if [ "$HASH" = "$STORED_HASH" ]; then
|
|
38
|
+
echo "No changes in iap_config.json (hash: ${HASH:0:12}...). Skipping."
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo "Changes detected in IAP config (hash: ${HASH:0:12}...)"
|
|
44
|
+
|
|
45
|
+
# --- Resolve service account path ---
|
|
46
|
+
SA_FULL_PATH="$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH"
|
|
47
|
+
if [ ! -f "$SA_FULL_PATH" ]; then
|
|
48
|
+
echo "ERROR: Service account JSON not found at $SA_FULL_PATH" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# --- Sync IAP via Fastlane ---
|
|
53
|
+
echo "Syncing Android IAP configuration..."
|
|
54
|
+
|
|
55
|
+
PACKAGE_NAME="$PACKAGE_NAME" \
|
|
56
|
+
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH" \
|
|
57
|
+
bundle exec fastlane sync_google_iap
|
|
58
|
+
|
|
59
|
+
echo "Android IAP sync complete"
|
|
60
|
+
|
|
61
|
+
# --- Update hash on success ---
|
|
62
|
+
echo "$HASH" > "$STATE_FILE"
|
|
63
|
+
echo "Updated state hash: ${HASH:0:12}..."
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
6
|
+
|
|
7
|
+
# --- Check Google Play readiness ---
|
|
8
|
+
if [ "${GOOGLE_PLAY_READY:-false}" != "true" ]; then
|
|
9
|
+
echo "ERROR: Google Play not ready. Cannot update data safety." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# --- Check if data safety CSV exists ---
|
|
14
|
+
DATA_SAFETY_CSV="$PROJECT_ROOT/fastlane/data_safety.csv"
|
|
15
|
+
if [ ! -f "$DATA_SAFETY_CSV" ]; then
|
|
16
|
+
echo "No data_safety.csv found at $DATA_SAFETY_CSV. Skipping."
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# --- Hash-based change detection ---
|
|
21
|
+
STATE_DIR="$PROJECT_ROOT/.ci-state"
|
|
22
|
+
mkdir -p "$STATE_DIR"
|
|
23
|
+
|
|
24
|
+
HASH=$(shasum -a 256 "$DATA_SAFETY_CSV" | cut -d' ' -f1)
|
|
25
|
+
STATE_FILE="$STATE_DIR/android-data-safety-hash"
|
|
26
|
+
|
|
27
|
+
if [ -f "$STATE_FILE" ]; then
|
|
28
|
+
STORED_HASH=$(cat "$STATE_FILE")
|
|
29
|
+
if [ "$HASH" = "$STORED_HASH" ]; then
|
|
30
|
+
echo "No changes in data_safety.csv (hash: ${HASH:0:12}...). Skipping."
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
echo "Changes detected in data safety config (hash: ${HASH:0:12}...)"
|
|
36
|
+
|
|
37
|
+
# --- Link fastlane directories ---
|
|
38
|
+
"$SCRIPT_DIR/../common/link-fastlane.sh" android
|
|
39
|
+
|
|
40
|
+
# --- Ensure fastlane is installed ---
|
|
41
|
+
cd "$APP_ROOT/android"
|
|
42
|
+
if ! bundle exec fastlane --version >/dev/null 2>&1; then
|
|
43
|
+
"$SCRIPT_DIR/../common/install-fastlane.sh" android
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# --- Resolve service account path ---
|
|
47
|
+
SA_FULL_PATH="$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH"
|
|
48
|
+
if [ ! -f "$SA_FULL_PATH" ]; then
|
|
49
|
+
echo "ERROR: Service account JSON not found at $SA_FULL_PATH" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# --- Update data safety via Fastlane ---
|
|
54
|
+
echo "Updating Android data safety..."
|
|
55
|
+
|
|
56
|
+
PACKAGE_NAME="$PACKAGE_NAME" \
|
|
57
|
+
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH" \
|
|
58
|
+
DATA_SAFETY_CSV_PATH="$DATA_SAFETY_CSV" \
|
|
59
|
+
bundle exec fastlane update_data_safety
|
|
60
|
+
|
|
61
|
+
echo "Android data safety update complete"
|
|
62
|
+
|
|
63
|
+
# --- Update hash on success ---
|
|
64
|
+
echo "$HASH" > "$STATE_FILE"
|
|
65
|
+
echo "Updated state hash: ${HASH:0:12}..."
|