@daemux/store-automator 0.7.0 → 0.8.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 (40) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +6 -2
  3. package/bin/cli.mjs +43 -15
  4. package/package.json +1 -1
  5. package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
  6. package/src/ci-config.mjs +33 -1
  7. package/src/install-paths.mjs +93 -0
  8. package/src/install.mjs +39 -78
  9. package/src/prompt.mjs +31 -7
  10. package/src/templates.mjs +71 -1
  11. package/templates/Matchfile.template +8 -0
  12. package/templates/ci.config.yaml.template +3 -0
  13. package/templates/codemagic.template.yaml +329 -111
  14. package/templates/fastlane/android/Fastfile.template +56 -8
  15. package/templates/fastlane/android/Pluginfile.template +4 -1
  16. package/templates/fastlane/ios/Fastfile.template +72 -17
  17. package/templates/fastlane/ios/Pluginfile.template +4 -1
  18. package/templates/github/workflows/android-release.yml +72 -0
  19. package/templates/github/workflows/ios-release.yml +62 -0
  20. package/templates/scripts/ci/android/build.sh +50 -0
  21. package/templates/scripts/ci/android/check-readiness.sh +99 -0
  22. package/templates/scripts/ci/android/manage-version.sh +133 -0
  23. package/templates/scripts/ci/android/setup-keystore.sh +80 -0
  24. package/templates/scripts/ci/android/sync-iap.sh +63 -0
  25. package/templates/scripts/ci/android/update-data-safety.sh +65 -0
  26. package/templates/scripts/ci/android/upload-binary.sh +62 -0
  27. package/templates/scripts/ci/android/upload-metadata.sh +59 -0
  28. package/templates/scripts/ci/common/check-changed.sh +74 -0
  29. package/templates/scripts/ci/common/flutter-setup.sh +22 -0
  30. package/templates/scripts/ci/common/install-fastlane.sh +39 -0
  31. package/templates/scripts/ci/common/link-fastlane.sh +56 -0
  32. package/templates/scripts/ci/common/read-config.sh +71 -0
  33. package/templates/scripts/ci/ios/build.sh +52 -0
  34. package/templates/scripts/ci/ios/manage-version.sh +64 -0
  35. package/templates/scripts/ci/ios/set-build-number.sh +95 -0
  36. package/templates/scripts/ci/ios/setup-signing.sh +242 -0
  37. package/templates/scripts/ci/ios/sync-iap.sh +91 -0
  38. package/templates/scripts/ci/ios/upload-binary.sh +44 -0
  39. package/templates/scripts/ci/ios/upload-metadata.sh +92 -0
  40. package/templates/scripts/update_data_safety.py +220 -0
@@ -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 "Google Play not ready. Skipping data safety update."
10
+ exit 0
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}..."
@@ -0,0 +1,62 @@
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 ""
10
+ echo "============================================="
11
+ echo " Google Play is NOT ready for automation"
12
+ echo "============================================="
13
+ echo ""
14
+ echo "This is likely the first release. You must upload the AAB manually."
15
+
16
+ AAB_DIR="$APP_ROOT/build/app/outputs/bundle/release"
17
+ AAB_FILE=$(find "$AAB_DIR" -name "*.aab" -type f 2>/dev/null | head -1)
18
+ if [ -n "$AAB_FILE" ]; then
19
+ echo ""
20
+ echo "Built AAB: $AAB_FILE ($(du -h "$AAB_FILE" | cut -f1))"
21
+ else
22
+ echo ""
23
+ echo "No AAB found. Run build.sh first."
24
+ fi
25
+
26
+ echo ""
27
+ echo "Manual upload steps:"
28
+ echo " 1. Go to Google Play Console: https://play.google.com/console"
29
+ echo " 2. Select your app ($PACKAGE_NAME)"
30
+ echo " 3. Go to Release > Testing > Internal testing (or your target track)"
31
+ echo " 4. Create a new release and upload the AAB file"
32
+ echo " 5. Complete the release form and roll out"
33
+ echo ""
34
+ echo "After the first manual upload, subsequent releases will be automated."
35
+ exit 1
36
+ fi
37
+
38
+ # --- Validate prerequisites ---
39
+ SA_FULL_PATH="$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH"
40
+ if [ ! -f "$SA_FULL_PATH" ]; then
41
+ echo "ERROR: Service account JSON not found at $SA_FULL_PATH" >&2
42
+ exit 1
43
+ fi
44
+
45
+ # --- Link fastlane directories and ensure installed ---
46
+ "$SCRIPT_DIR/../common/link-fastlane.sh" android
47
+ cd "$APP_ROOT/android"
48
+ if ! bundle exec fastlane --version >/dev/null 2>&1; then
49
+ "$SCRIPT_DIR/../common/install-fastlane.sh" android
50
+ fi
51
+
52
+ # --- Upload via Fastlane ---
53
+ echo "Uploading AAB to Google Play (track: ${TRACK:-internal}, package: $PACKAGE_NAME)..."
54
+
55
+ PACKAGE_NAME="$PACKAGE_NAME" \
56
+ GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH" \
57
+ TRACK="${TRACK:-internal}" \
58
+ ROLLOUT_FRACTION="${ROLLOUT_FRACTION:-}" \
59
+ IN_APP_UPDATE_PRIORITY="${IN_APP_UPDATE_PRIORITY:-0}" \
60
+ bundle exec fastlane upload_binary_android
61
+
62
+ echo "Android binary upload complete"
@@ -0,0 +1,59 @@
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 "Google Play not ready. Skipping metadata upload."
10
+ echo "Run check-readiness.sh for details."
11
+ exit 0
12
+ fi
13
+
14
+ # --- Hash-based change detection ---
15
+ STATE_DIR="$PROJECT_ROOT/.ci-state"
16
+ mkdir -p "$STATE_DIR"
17
+
18
+ HASH=$(find "$PROJECT_ROOT/fastlane/metadata" "$PROJECT_ROOT/fastlane/screenshots/android" \
19
+ -type f 2>/dev/null | sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
20
+ STATE_FILE="$STATE_DIR/android-metadata-hash"
21
+
22
+ if [ -f "$STATE_FILE" ]; then
23
+ STORED_HASH=$(cat "$STATE_FILE")
24
+ if [ "$HASH" = "$STORED_HASH" ]; then
25
+ echo "No changes in Android metadata or screenshots (hash: ${HASH:0:12}...). Skipping."
26
+ exit 0
27
+ fi
28
+ fi
29
+
30
+ echo "Changes detected in Android metadata (hash: ${HASH:0:12}...)"
31
+
32
+ # --- Link fastlane directories ---
33
+ "$SCRIPT_DIR/../common/link-fastlane.sh" android
34
+
35
+ # --- Ensure fastlane is installed ---
36
+ cd "$APP_ROOT/android"
37
+ if ! bundle exec fastlane --version >/dev/null 2>&1; then
38
+ "$SCRIPT_DIR/../common/install-fastlane.sh" android
39
+ fi
40
+
41
+ # --- Resolve service account path ---
42
+ SA_FULL_PATH="$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH"
43
+ if [ ! -f "$SA_FULL_PATH" ]; then
44
+ echo "ERROR: Service account JSON not found at $SA_FULL_PATH" >&2
45
+ exit 1
46
+ fi
47
+
48
+ # --- Upload metadata via Fastlane ---
49
+ echo "Uploading Android metadata..."
50
+
51
+ PACKAGE_NAME="$PACKAGE_NAME" \
52
+ GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH" \
53
+ bundle exec fastlane upload_metadata_android
54
+
55
+ echo "Android metadata uploaded successfully"
56
+
57
+ # --- Update hash on success ---
58
+ echo "$HASH" > "$STATE_FILE"
59
+ echo "Updated state hash: ${HASH:0:12}..."
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Content-based change detection using SHA256 hashes.
5
+ # Stores hashes in .ci-state/ directory (gitignored).
6
+ # Exit 0 = changed (or first run) -> proceed with work
7
+ # Exit 1 = unchanged -> skip work
8
+ #
9
+ # Usage: scripts/ci/common/check-changed.sh <directory-path> [--use-cache]
10
+ #
11
+ # Hash update pattern:
12
+ # 1. Call check-changed.sh <path> -- if exit 1, skip.
13
+ # 2. Do the work (upload, sync, etc.)
14
+ # 3. On success: mv .ci-state/<key>.hash.pending .ci-state/<key>.hash
15
+ # 4. On failure: leave .pending file (next run will see change again)
16
+
17
+ USE_CACHE=false
18
+ TARGET=""
19
+ while [ $# -gt 0 ]; do
20
+ case "$1" in
21
+ --use-cache) USE_CACHE=true ;;
22
+ *) TARGET="$1" ;;
23
+ esac
24
+ shift
25
+ done
26
+
27
+ if [ -z "$TARGET" ]; then
28
+ echo "Usage: check-changed.sh <directory-path> [--use-cache]" >&2
29
+ exit 1
30
+ fi
31
+
32
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
34
+
35
+ STATE_DIR="$PROJECT_ROOT/.ci-state"
36
+ mkdir -p "$STATE_DIR"
37
+
38
+ # When running locally without --use-cache, always report changed
39
+ if [ -z "${CI:-}" ] && [ "$USE_CACHE" = "false" ]; then
40
+ echo "Local mode (no --use-cache): reporting changed"
41
+ exit 0
42
+ fi
43
+
44
+ # Generate a safe filename from the target path
45
+ STATE_KEY=$(echo "$TARGET" | sed 's/[^a-zA-Z0-9]/_/g')
46
+ STATE_FILE="$STATE_DIR/$STATE_KEY.hash"
47
+
48
+ # Check if any files exist before hashing
49
+ FILE_COUNT=$(find "$PROJECT_ROOT/$TARGET" -type f 2>/dev/null | wc -l | tr -d ' ')
50
+ if [ "$FILE_COUNT" -eq 0 ]; then
51
+ echo "No files found matching '$TARGET'. Reporting changed (first run)."
52
+ exit 0
53
+ fi
54
+
55
+ # Compute current content hash using a direct pipeline.
56
+ # NOTE: Using `while read` to safely handle filenames with spaces.
57
+ CURRENT_HASH=$(
58
+ find "$PROJECT_ROOT/$TARGET" -type f 2>/dev/null | sort \
59
+ | while IFS= read -r file; do shasum -a 256 "$file"; done \
60
+ | shasum -a 256 | cut -d' ' -f1
61
+ )
62
+
63
+ # Compare with stored hash
64
+ if [ -f "$STATE_FILE" ]; then
65
+ STORED_HASH=$(cat "$STATE_FILE")
66
+ if [ "$CURRENT_HASH" = "$STORED_HASH" ]; then
67
+ echo "No changes detected for '$TARGET' (hash: ${CURRENT_HASH:0:12}...)"
68
+ exit 1
69
+ fi
70
+ fi
71
+
72
+ echo "Changes detected for '$TARGET' (hash: ${CURRENT_HASH:0:12}...)"
73
+ echo "$CURRENT_HASH" > "$STATE_FILE.pending"
74
+ exit 0
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Runs Flutter pub get and verifies the SDK is available.
5
+ # Usage: scripts/ci/common/flutter-setup.sh
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$SCRIPT_DIR/read-config.sh"
9
+
10
+ if ! command -v flutter &>/dev/null; then
11
+ echo "ERROR: Flutter SDK not found in PATH" >&2
12
+ exit 1
13
+ fi
14
+
15
+ echo "Flutter version:"
16
+ flutter --version
17
+
18
+ echo "Running flutter pub get in $APP_ROOT..."
19
+ cd "$APP_ROOT"
20
+ flutter pub get
21
+
22
+ echo "Flutter setup complete"
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Installs Fastlane and its dependencies for the given platform.
5
+ # Usage: scripts/ci/common/install-fastlane.sh <ios|android>
6
+
7
+ PLATFORM="${1:?Usage: install-fastlane.sh <ios|android>}"
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ source "$SCRIPT_DIR/read-config.sh"
11
+
12
+ PLATFORM_DIR="$APP_ROOT/$PLATFORM"
13
+
14
+ if [ ! -d "$PLATFORM_DIR" ]; then
15
+ echo "ERROR: Platform directory not found: $PLATFORM_DIR" >&2
16
+ exit 1
17
+ fi
18
+
19
+ GEMFILE="$PLATFORM_DIR/Gemfile"
20
+ if [ ! -f "$GEMFILE" ]; then
21
+ echo "ERROR: Gemfile not found at $GEMFILE" >&2
22
+ exit 1
23
+ fi
24
+
25
+ echo "Installing Fastlane for $PLATFORM..."
26
+ cd "$PLATFORM_DIR"
27
+
28
+ if ! command -v bundle &>/dev/null; then
29
+ echo "Installing bundler..."
30
+ gem install bundler --no-document --user-install
31
+ fi
32
+
33
+ export BUNDLE_PATH="${BUNDLE_PATH:-vendor/bundle}"
34
+ bundle install --jobs 4 --retry 3
35
+
36
+ echo "Verifying Fastlane installation..."
37
+ bundle exec fastlane --version
38
+
39
+ echo "Fastlane installed successfully for $PLATFORM"
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Creates symlinks so that `cd app/ios && bundle exec fastlane` (or android)
5
+ # can find the Fastfile. Fastlane config lives at project root under fastlane/ios/
6
+ # and fastlane/android/. This script links them into the platform directories.
7
+ #
8
+ # Usage: scripts/ci/common/link-fastlane.sh <ios|android>
9
+
10
+ PLATFORM="${1:?Usage: link-fastlane.sh <ios|android>}"
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ source "$SCRIPT_DIR/read-config.sh"
14
+
15
+ create_symlink() {
16
+ local target="$1"
17
+ local link="$2"
18
+
19
+ if [ -e "$link" ] && [ ! -L "$link" ]; then
20
+ echo "WARNING: $link exists and is not a symlink. Skipping." >&2
21
+ return 1
22
+ fi
23
+
24
+ ln -sfn "$target" "$link"
25
+ echo "Linked: $link -> $target"
26
+
27
+ if [ ! -e "$link" ]; then
28
+ echo "ERROR: Symlink target does not exist: $target" >&2
29
+ return 1
30
+ fi
31
+ }
32
+
33
+ echo "Linking Fastlane directories for $PLATFORM..."
34
+
35
+ FASTLANE_ROOT="$PROJECT_ROOT/fastlane"
36
+ PLATFORM_DIR="$APP_ROOT/$PLATFORM"
37
+
38
+ if [ ! -d "$FASTLANE_ROOT/$PLATFORM" ]; then
39
+ echo "ERROR: Fastlane config not found at $FASTLANE_ROOT/$PLATFORM" >&2
40
+ exit 1
41
+ fi
42
+
43
+ if [ ! -d "$PLATFORM_DIR" ]; then
44
+ echo "ERROR: Platform directory not found at $PLATFORM_DIR" >&2
45
+ exit 1
46
+ fi
47
+
48
+ # Create symlink: app/ios/fastlane -> ../../fastlane/ios
49
+ # Create symlink: app/android/fastlane -> ../../fastlane/android
50
+ RELATIVE_TARGET="../../fastlane/$PLATFORM"
51
+ LINK_PATH="$PLATFORM_DIR/fastlane"
52
+
53
+ create_symlink "$RELATIVE_TARGET" "$LINK_PATH"
54
+
55
+ echo "Fastlane linking complete for $PLATFORM"
56
+ ls -la "$LINK_PATH"
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Reads values from ci.config.yaml using yq and exports them as environment variables.
5
+ # Usage: source scripts/ci/common/read-config.sh
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
9
+ export PROJECT_ROOT
10
+
11
+ CONFIG="$PROJECT_ROOT/ci.config.yaml"
12
+
13
+ if [ ! -f "$CONFIG" ]; then
14
+ echo "ERROR: ci.config.yaml not found at $CONFIG" >&2
15
+ exit 1
16
+ fi
17
+
18
+ if ! command -v yq &>/dev/null; then
19
+ echo "ERROR: yq is not installed. Install with: brew install yq" >&2
20
+ exit 1
21
+ fi
22
+
23
+ # App root
24
+ export FLUTTER_ROOT=$(yq '.flutter_root // "."' "$CONFIG")
25
+ export APP_ROOT="$PROJECT_ROOT/$FLUTTER_ROOT"
26
+
27
+ # App identity
28
+ export BUNDLE_ID=$(yq '.app.bundle_id // ""' "$CONFIG")
29
+ export PACKAGE_NAME=$(yq '.app.package_name // ""' "$CONFIG")
30
+ export APP_NAME=$(yq '.app.name // ""' "$CONFIG")
31
+ export SKU=$(yq '.app.sku // ""' "$CONFIG")
32
+ export APPLE_ID=$(yq '.app.apple_id // ""' "$CONFIG")
33
+
34
+ # Credentials - Apple
35
+ export P8_KEY_PATH=$(yq '.credentials.apple.p8_key_path // ""' "$CONFIG")
36
+ export APPLE_KEY_ID=$(yq '.credentials.apple.key_id // ""' "$CONFIG")
37
+ export APPLE_ISSUER_ID=$(yq '.credentials.apple.issuer_id // ""' "$CONFIG")
38
+
39
+ # Credentials - Google
40
+ export GOOGLE_SA_JSON_PATH=$(yq '.credentials.google.service_account_json_path // ""' "$CONFIG")
41
+
42
+ # Credentials - Android signing
43
+ export KEYSTORE_PASSWORD=$(yq '.credentials.android.keystore_password // ""' "$CONFIG")
44
+
45
+ # Credentials - Match (iOS code signing)
46
+ export MATCH_GIT_URL=$(yq '.credentials.match.git_url // ""' "$CONFIG")
47
+ export MATCH_DEPLOY_KEY_PATH=$(yq '.credentials.match.deploy_key_path // ""' "$CONFIG")
48
+
49
+ # iOS App Store settings
50
+ export PRIMARY_CATEGORY=$(yq '.ios.primary_category // ""' "$CONFIG")
51
+ export SECONDARY_CATEGORY=$(yq '.ios.secondary_category // ""' "$CONFIG")
52
+ export PRICE_TIER=$(yq '.ios.price_tier // ""' "$CONFIG")
53
+ export SUBMIT_FOR_REVIEW=$(yq '.ios.submit_for_review // "false"' "$CONFIG")
54
+ export AUTOMATIC_RELEASE=$(yq '.ios.automatic_release // "false"' "$CONFIG")
55
+
56
+ # Android Play Store settings
57
+ export TRACK=$(yq '.android.track // "internal"' "$CONFIG")
58
+ export ROLLOUT_FRACTION=$(yq '.android.rollout_fraction // ""' "$CONFIG")
59
+ export IN_APP_UPDATE_PRIORITY=$(yq '.android.in_app_update_priority // "0"' "$CONFIG")
60
+
61
+ # Codemagic CI/CD
62
+ export CODEMAGIC_APP_ID=$(yq '.codemagic.app_id // ""' "$CONFIG")
63
+ export CODEMAGIC_TEAM_ID=$(yq '.codemagic.team_id // ""' "$CONFIG")
64
+
65
+ # Web
66
+ export WEB_DOMAIN=$(yq '.web.domain // ""' "$CONFIG")
67
+
68
+ echo "Config loaded from $CONFIG"
69
+ echo " APP_ROOT=$APP_ROOT"
70
+ echo " BUNDLE_ID=$BUNDLE_ID"
71
+ echo " PACKAGE_NAME=$PACKAGE_NAME"
@@ -0,0 +1,52 @@
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 "${EXPORT_OPTIONS_PLIST:-}" ]; then
9
+ echo "ERROR: EXPORT_OPTIONS_PLIST not set. Run setup-signing.sh first (TASK_3)." >&2
10
+ exit 1
11
+ fi
12
+
13
+ if [ ! -f "$EXPORT_OPTIONS_PLIST" ]; then
14
+ echo "ERROR: Export options plist not found at $EXPORT_OPTIONS_PLIST" >&2
15
+ exit 1
16
+ fi
17
+
18
+ if ! command -v flutter &>/dev/null; then
19
+ echo "ERROR: Flutter SDK not found in PATH" >&2
20
+ exit 1
21
+ fi
22
+
23
+ # --- Build IPA ---
24
+ echo "Building IPA..."
25
+ echo " APP_ROOT: $APP_ROOT"
26
+ echo " EXPORT_OPTIONS_PLIST: $EXPORT_OPTIONS_PLIST"
27
+
28
+ cd "$APP_ROOT"
29
+ flutter build ipa \
30
+ --release \
31
+ --export-options-plist="$EXPORT_OPTIONS_PLIST"
32
+
33
+ # --- Verify IPA exists ---
34
+ IPA_DIR="$APP_ROOT/build/ios/ipa"
35
+ IPA_FILE=$(find "$IPA_DIR" -name "*.ipa" -type f 2>/dev/null | head -1)
36
+ if [ -z "$IPA_FILE" ]; then
37
+ echo "ERROR: No .ipa file found in $IPA_DIR" >&2
38
+ exit 1
39
+ fi
40
+
41
+ echo "IPA built successfully: $IPA_FILE"
42
+ echo "IPA size: $(du -h "$IPA_FILE" | cut -f1)"
43
+
44
+ # --- Export ---
45
+ export IPA_PATH="$IPA_FILE"
46
+
47
+ if [ -n "${GITHUB_ENV:-}" ]; then
48
+ echo "IPA_PATH=$IPA_FILE" >> "$GITHUB_ENV"
49
+ echo "Exported IPA_PATH to GITHUB_ENV"
50
+ fi
51
+
52
+ echo "iOS build complete: $IPA_PATH"
@@ -0,0 +1,64 @@
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 required config ---
8
+ if [ -z "$APPLE_KEY_ID" ] || [ -z "$APPLE_ISSUER_ID" ] || [ -z "$P8_KEY_PATH" ]; then
9
+ echo "ERROR: Missing Apple credentials in ci.config.yaml (APPLE_KEY_ID, APPLE_ISSUER_ID, or P8_KEY_PATH)" >&2
10
+ exit 1
11
+ fi
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
+ # --- Set ASC API env vars for manage_version_ios.py ---
20
+ export APP_STORE_CONNECT_KEY_IDENTIFIER="$APPLE_KEY_ID"
21
+ export APP_STORE_CONNECT_ISSUER_ID="$APPLE_ISSUER_ID"
22
+ export APP_STORE_CONNECT_PRIVATE_KEY
23
+ APP_STORE_CONNECT_PRIVATE_KEY=$(cat "$P8_FULL_PATH")
24
+
25
+ # --- Export BUNDLE_ID for manage_version_ios.py ---
26
+ export BUNDLE_ID
27
+
28
+ # --- Run the existing Python script ---
29
+ echo "Running manage_version_ios.py..."
30
+ VERSION_JSON=$(python3 "$PROJECT_ROOT/scripts/manage_version_ios.py")
31
+
32
+ if [ -z "$VERSION_JSON" ]; then
33
+ echo "ERROR: manage_version_ios.py returned empty output" >&2
34
+ exit 1
35
+ fi
36
+
37
+ echo "Version info: $VERSION_JSON"
38
+
39
+ # --- Parse JSON output ---
40
+ # Expected format: {"version": "X.Y.Z", "version_id": "...", "state": "..."}
41
+ read -r APP_VERSION APP_VERSION_ID APP_STATUS < <(
42
+ echo "$VERSION_JSON" | python3 -c "
43
+ import sys, json
44
+ d = json.load(sys.stdin)
45
+ print(d['version'], d.get('version_id',''), d.get('state',''))
46
+ "
47
+ )
48
+
49
+ echo "Parsed version: $APP_VERSION (id: $APP_VERSION_ID, status: $APP_STATUS)"
50
+
51
+ # --- Export to environment ---
52
+ export APP_VERSION
53
+ export APP_VERSION_ID
54
+ export APP_STATUS
55
+
56
+ # Write to $GITHUB_ENV for inter-step communication in CI
57
+ if [ -n "${GITHUB_ENV:-}" ]; then
58
+ echo "APP_VERSION=$APP_VERSION" >> "$GITHUB_ENV"
59
+ echo "APP_VERSION_ID=$APP_VERSION_ID" >> "$GITHUB_ENV"
60
+ echo "APP_STATUS=$APP_STATUS" >> "$GITHUB_ENV"
61
+ echo "Exported to GITHUB_ENV"
62
+ fi
63
+
64
+ echo "iOS version management complete: $APP_VERSION"
@@ -0,0 +1,95 @@
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 "${APP_VERSION:-}" ]; then
9
+ echo "ERROR: APP_VERSION not set. Run manage-version.sh first." >&2
10
+ exit 1
11
+ fi
12
+
13
+ if [ -z "$BUNDLE_ID" ]; then
14
+ echo "ERROR: BUNDLE_ID not set in ci.config.yaml" >&2
15
+ exit 1
16
+ fi
17
+
18
+ # --- Ensure ASC API env vars are set ---
19
+ if [ -z "${APP_STORE_CONNECT_KEY_IDENTIFIER:-}" ]; then
20
+ P8_FULL_PATH="$PROJECT_ROOT/$P8_KEY_PATH"
21
+ export APP_STORE_CONNECT_KEY_IDENTIFIER="$APPLE_KEY_ID"
22
+ export APP_STORE_CONNECT_ISSUER_ID="$APPLE_ISSUER_ID"
23
+ export APP_STORE_CONNECT_PRIVATE_KEY
24
+ APP_STORE_CONNECT_PRIVATE_KEY=$(cat "$P8_FULL_PATH")
25
+ fi
26
+
27
+ # --- Ensure PyJWT is installed ---
28
+ pip3 install PyJWT >/dev/null 2>&1 || true
29
+
30
+ # --- Fetch latest build number from ASC ---
31
+ echo "Fetching latest build number from App Store Connect..."
32
+
33
+ LATEST_BUILD=$(python3 -c "
34
+ import os, json, time, jwt, urllib.request
35
+
36
+ key_id = os.environ['APP_STORE_CONNECT_KEY_IDENTIFIER']
37
+ issuer_id = os.environ['APP_STORE_CONNECT_ISSUER_ID']
38
+ private_key = os.environ['APP_STORE_CONNECT_PRIVATE_KEY']
39
+ bundle_id = os.environ.get('BUNDLE_ID', '')
40
+
41
+ # Generate JWT token
42
+ now = int(time.time())
43
+ payload = {'iss': issuer_id, 'iat': now, 'exp': now + 1200, 'aud': 'appstoreconnect-v1'}
44
+ token = jwt.encode(payload, private_key, algorithm='ES256', headers={'kid': key_id})
45
+ headers = {'Authorization': f'Bearer {token}'}
46
+
47
+ try:
48
+ # Resolve app ID from bundle ID
49
+ req = urllib.request.Request(
50
+ f'https://api.appstoreconnect.apple.com/v1/apps?filter[bundleId]={bundle_id}',
51
+ headers=headers)
52
+ with urllib.request.urlopen(req) as resp:
53
+ app_id = json.loads(resp.read())['data'][0]['id']
54
+
55
+ # Fetch latest build
56
+ req = urllib.request.Request(
57
+ f'https://api.appstoreconnect.apple.com/v1/builds?filter[app]={app_id}&sort=-version&limit=1',
58
+ headers=headers)
59
+ with urllib.request.urlopen(req) as resp:
60
+ builds = json.loads(resp.read())['data']
61
+
62
+ print(builds[0]['attributes']['version'] if builds else '0')
63
+ except Exception:
64
+ print('0')
65
+ " 2>/dev/null || echo "0")
66
+
67
+ echo "Latest build number from ASC: $LATEST_BUILD"
68
+
69
+ # --- Increment build number ---
70
+ BUILD_NUMBER=$((LATEST_BUILD + 1))
71
+ echo "New build number: $BUILD_NUMBER"
72
+
73
+ # --- Write to pubspec.yaml ---
74
+ PUBSPEC="$APP_ROOT/pubspec.yaml"
75
+ if [ ! -f "$PUBSPEC" ]; then
76
+ echo "ERROR: pubspec.yaml not found at $PUBSPEC" >&2
77
+ exit 1
78
+ fi
79
+
80
+ echo "Updating pubspec.yaml: version: ${APP_VERSION}+${BUILD_NUMBER}"
81
+ sed -i '' "s/^version: .*/version: ${APP_VERSION}+${BUILD_NUMBER}/" "$PUBSPEC"
82
+
83
+ # Verify the write
84
+ WRITTEN_VERSION=$(grep '^version:' "$PUBSPEC" | head -1)
85
+ echo "Written to pubspec.yaml: $WRITTEN_VERSION"
86
+
87
+ # --- Export ---
88
+ export BUILD_NUMBER
89
+
90
+ if [ -n "${GITHUB_ENV:-}" ]; then
91
+ echo "BUILD_NUMBER=$BUILD_NUMBER" >> "$GITHUB_ENV"
92
+ echo "Exported BUILD_NUMBER to GITHUB_ENV"
93
+ fi
94
+
95
+ echo "Build number set: $BUILD_NUMBER"