@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.
Files changed (46) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/bin/cli.mjs +38 -14
  3. package/package.json +1 -1
  4. package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
  5. package/plugins/store-automator/agents/appstore-meta-creator.md +13 -8
  6. package/plugins/store-automator/agents/architect.md +42 -1
  7. package/plugins/store-automator/agents/devops.md +26 -27
  8. package/scripts/check_google_play.py +1 -1
  9. package/scripts/manage_version_ios.py +1 -1
  10. package/src/ci-config.mjs +21 -0
  11. package/src/install-paths.mjs +92 -0
  12. package/src/install.mjs +33 -78
  13. package/src/templates.mjs +71 -1
  14. package/templates/CLAUDE.md.template +70 -16
  15. package/templates/Matchfile.template +8 -0
  16. package/templates/ci.config.yaml.template +3 -0
  17. package/templates/codemagic.template.yaml +131 -96
  18. package/templates/fastlane/android/Fastfile.template +23 -5
  19. package/templates/fastlane/ios/Fastfile.template +4 -3
  20. package/templates/github/workflows/android-release.yml +141 -0
  21. package/templates/github/workflows/codemagic-trigger.yml +15 -5
  22. package/templates/github/workflows/ios-release.yml +119 -0
  23. package/templates/scripts/check_google_play.py +1 -1
  24. package/templates/scripts/ci/android/build.sh +50 -0
  25. package/templates/scripts/ci/android/check-readiness.sh +93 -0
  26. package/templates/scripts/ci/android/manage-version.sh +134 -0
  27. package/templates/scripts/ci/android/setup-keystore.sh +80 -0
  28. package/templates/scripts/ci/android/sync-iap.sh +63 -0
  29. package/templates/scripts/ci/android/update-data-safety.sh +65 -0
  30. package/templates/scripts/ci/android/upload-binary.sh +62 -0
  31. package/templates/scripts/ci/android/upload-metadata.sh +77 -0
  32. package/templates/scripts/ci/common/check-changed.sh +74 -0
  33. package/templates/scripts/ci/common/flutter-setup.sh +22 -0
  34. package/templates/scripts/ci/common/install-fastlane.sh +39 -0
  35. package/templates/scripts/ci/common/link-fastlane.sh +56 -0
  36. package/templates/scripts/ci/common/read-config.sh +74 -0
  37. package/templates/scripts/ci/ios/build.sh +91 -0
  38. package/templates/scripts/ci/ios/manage-version.sh +64 -0
  39. package/templates/scripts/ci/ios/set-build-number.sh +92 -0
  40. package/templates/scripts/ci/ios/setup-signing.sh +283 -0
  41. package/templates/scripts/ci/ios/sync-iap.sh +80 -0
  42. package/templates/scripts/ci/ios/upload-binary.sh +43 -0
  43. package/templates/scripts/ci/ios/upload-metadata.sh +82 -0
  44. package/templates/scripts/create_app_record.py +1 -1
  45. package/templates/scripts/manage_version_ios.py +1 -1
  46. package/templates/scripts/update_data_safety.py +220 -0
@@ -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,77 @@
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 upload metadata." >&2
10
+ echo "Run check-readiness.sh for details." >&2
11
+ exit 1
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
+ set +e
52
+ FASTLANE_OUTPUT=$(PACKAGE_NAME="$PACKAGE_NAME" \
53
+ GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH" \
54
+ bundle exec fastlane upload_metadata_android 2>&1)
55
+ FASTLANE_EXIT=$?
56
+ set -e
57
+
58
+ echo "$FASTLANE_OUTPUT"
59
+
60
+ if [ $FASTLANE_EXIT -ne 0 ]; then
61
+ # Google Play API rejects non-draft edits on apps that have never been
62
+ # published. This resolves after the first manual release via the Play
63
+ # Console. Hard-fail so the CI run surfaces the issue immediately.
64
+ if echo "$FASTLANE_OUTPUT" | grep -qi "draft app"; then
65
+ echo "ERROR: Metadata upload failed because the app is still in draft" >&2
66
+ echo " state on Google Play. Complete the first release manually" >&2
67
+ echo " via the Play Console, then re-run this workflow." >&2
68
+ exit 1
69
+ fi
70
+ exit $FASTLANE_EXIT
71
+ fi
72
+
73
+ echo "Android metadata uploaded successfully"
74
+
75
+ # --- Update hash on success ---
76
+ echo "$HASH" > "$STATE_FILE"
77
+ 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
+ bundle config set --local 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 "ERROR: $link exists and is not a symlink. Cannot create link." >&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,74 @@
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
+ # Fastlane expects CM_BUILD_DIR for absolute path resolution in Fastfiles
28
+ export CM_BUILD_DIR="$PROJECT_ROOT"
29
+
30
+ # App identity
31
+ export BUNDLE_ID=$(yq '.app.bundle_id // ""' "$CONFIG")
32
+ export PACKAGE_NAME=$(yq '.app.package_name // ""' "$CONFIG")
33
+ export APP_NAME=$(yq '.app.name // ""' "$CONFIG")
34
+ export SKU=$(yq '.app.sku // ""' "$CONFIG")
35
+ export APPLE_ID=$(yq '.app.apple_id // ""' "$CONFIG")
36
+
37
+ # Credentials - Apple
38
+ export P8_KEY_PATH=$(yq '.credentials.apple.p8_key_path // ""' "$CONFIG")
39
+ export APPLE_KEY_ID=$(yq '.credentials.apple.key_id // ""' "$CONFIG")
40
+ export APPLE_ISSUER_ID=$(yq '.credentials.apple.issuer_id // ""' "$CONFIG")
41
+
42
+ # Credentials - Google
43
+ export GOOGLE_SA_JSON_PATH=$(yq '.credentials.google.service_account_json_path // ""' "$CONFIG")
44
+
45
+ # Credentials - Android signing
46
+ export KEYSTORE_PASSWORD=$(yq '.credentials.android.keystore_password // ""' "$CONFIG")
47
+
48
+ # Credentials - Match (iOS code signing)
49
+ export MATCH_GIT_URL=$(yq '.credentials.match.git_url // ""' "$CONFIG")
50
+ export MATCH_DEPLOY_KEY_PATH=$(yq '.credentials.match.deploy_key_path // ""' "$CONFIG")
51
+
52
+ # iOS App Store settings
53
+ export PRIMARY_CATEGORY=$(yq '.ios.primary_category // ""' "$CONFIG")
54
+ export SECONDARY_CATEGORY=$(yq '.ios.secondary_category // ""' "$CONFIG")
55
+ export PRICE_TIER=$(yq '.ios.price_tier // ""' "$CONFIG")
56
+ export SUBMIT_FOR_REVIEW=$(yq '.ios.submit_for_review // "false"' "$CONFIG")
57
+ export AUTOMATIC_RELEASE=$(yq '.ios.automatic_release // "false"' "$CONFIG")
58
+
59
+ # Android Play Store settings
60
+ export TRACK=$(yq '.android.track // "internal"' "$CONFIG")
61
+ export ROLLOUT_FRACTION=$(yq '.android.rollout_fraction // ""' "$CONFIG")
62
+ export IN_APP_UPDATE_PRIORITY=$(yq '.android.in_app_update_priority // "0"' "$CONFIG")
63
+
64
+ # Codemagic CI/CD
65
+ export CODEMAGIC_APP_ID=$(yq '.codemagic.app_id // ""' "$CONFIG")
66
+ export CODEMAGIC_TEAM_ID=$(yq '.codemagic.team_id // ""' "$CONFIG")
67
+
68
+ # Web
69
+ export WEB_DOMAIN=$(yq '.web.domain // ""' "$CONFIG")
70
+
71
+ echo "Config loaded from $CONFIG"
72
+ echo " APP_ROOT=$APP_ROOT"
73
+ echo " BUNDLE_ID=$BUNDLE_ID"
74
+ echo " PACKAGE_NAME=$PACKAGE_NAME"
@@ -0,0 +1,91 @@
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
+ # --- Patch Xcode project for manual signing in CI ---
24
+ # flutter build ipa runs xcodebuild archive before applying ExportOptions.plist.
25
+ # The archive step uses the project's build settings, so we must set manual signing
26
+ # with the correct team ID and provisioning profile from setup-signing.sh.
27
+ PBXPROJ="$APP_ROOT/ios/Runner.xcodeproj/project.pbxproj"
28
+ PROFILE_NAME="match AppStore $BUNDLE_ID"
29
+
30
+ if [ -f "$PBXPROJ" ]; then
31
+ echo "Patching Xcode project for CI signing..."
32
+
33
+ # Ensure all configurations use manual signing
34
+ sed -i '' 's/CODE_SIGN_STYLE = Automatic/CODE_SIGN_STYLE = Manual/g' "$PBXPROJ"
35
+
36
+ # Set the team ID discovered by setup-signing.sh
37
+ if [ -n "${TEAM_ID:-}" ]; then
38
+ sed -i '' "s/DEVELOPMENT_TEAM = \"[^\"]*\"/DEVELOPMENT_TEAM = \"$TEAM_ID\"/g" "$PBXPROJ"
39
+ sed -i '' "s/DEVELOPMENT_TEAM = ;/DEVELOPMENT_TEAM = \"$TEAM_ID\";/g" "$PBXPROJ"
40
+ echo " DEVELOPMENT_TEAM = $TEAM_ID"
41
+ fi
42
+
43
+ # Set the provisioning profile specifier for the Runner target
44
+ if [ -n "$PROFILE_NAME" ]; then
45
+ PKEY="PROVISIONING_PROFILE_SPECIFIER"
46
+ sed -i '' \
47
+ "s/$PKEY = \"[^\"]*\"/$PKEY = \"$PROFILE_NAME\"/g" "$PBXPROJ"
48
+ sed -i '' \
49
+ "s/$PKEY = ;/$PKEY = \"$PROFILE_NAME\";/g" "$PBXPROJ"
50
+ echo " $PKEY = $PROFILE_NAME"
51
+ fi
52
+
53
+ # Set the code sign identity for distribution
54
+ sed -i '' \
55
+ 's/"CODE_SIGN_IDENTITY\[sdk=iphoneos\*\]" = "iPhone Developer"/'\
56
+ '"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"/g' \
57
+ "$PBXPROJ"
58
+
59
+ echo "Xcode project patched for CI signing"
60
+ fi
61
+
62
+ # --- Build IPA ---
63
+ echo "Building IPA..."
64
+ echo " APP_ROOT: $APP_ROOT"
65
+ echo " EXPORT_OPTIONS_PLIST: $EXPORT_OPTIONS_PLIST"
66
+
67
+ cd "$APP_ROOT"
68
+ flutter build ipa \
69
+ --release \
70
+ --export-options-plist="$EXPORT_OPTIONS_PLIST"
71
+
72
+ # --- Verify IPA exists ---
73
+ IPA_DIR="$APP_ROOT/build/ios/ipa"
74
+ IPA_FILE=$(find "$IPA_DIR" -name "*.ipa" -type f 2>/dev/null | head -1)
75
+ if [ -z "$IPA_FILE" ]; then
76
+ echo "ERROR: No .ipa file found in $IPA_DIR" >&2
77
+ exit 1
78
+ fi
79
+
80
+ echo "IPA built successfully: $IPA_FILE"
81
+ echo "IPA size: $(du -h "$IPA_FILE" | cut -f1)"
82
+
83
+ # --- Export ---
84
+ export IPA_PATH="$IPA_FILE"
85
+
86
+ if [ -n "${GITHUB_ENV:-}" ]; then
87
+ echo "IPA_PATH=$IPA_FILE" >> "$GITHUB_ENV"
88
+ echo "Exported IPA_PATH to GITHUB_ENV"
89
+ fi
90
+
91
+ 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,92 @@
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 --break-system-packages PyJWT
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
+ # Resolve app ID from bundle ID
48
+ req = urllib.request.Request(
49
+ f'https://api.appstoreconnect.apple.com/v1/apps?filter[bundleId]={bundle_id}',
50
+ headers=headers)
51
+ with urllib.request.urlopen(req) as resp:
52
+ app_id = json.loads(resp.read())['data'][0]['id']
53
+
54
+ # Fetch latest build
55
+ req = urllib.request.Request(
56
+ f'https://api.appstoreconnect.apple.com/v1/builds?filter[app]={app_id}&sort=-version&limit=1',
57
+ headers=headers)
58
+ with urllib.request.urlopen(req) as resp:
59
+ builds = json.loads(resp.read())['data']
60
+
61
+ print(builds[0]['attributes']['version'] if builds else '0')
62
+ ")
63
+
64
+ echo "Latest build number from ASC: $LATEST_BUILD"
65
+
66
+ # --- Increment build number ---
67
+ BUILD_NUMBER=$((LATEST_BUILD + 1))
68
+ echo "New build number: $BUILD_NUMBER"
69
+
70
+ # --- Write to pubspec.yaml ---
71
+ PUBSPEC="$APP_ROOT/pubspec.yaml"
72
+ if [ ! -f "$PUBSPEC" ]; then
73
+ echo "ERROR: pubspec.yaml not found at $PUBSPEC" >&2
74
+ exit 1
75
+ fi
76
+
77
+ echo "Updating pubspec.yaml: version: ${APP_VERSION}+${BUILD_NUMBER}"
78
+ sed -i '' "s/^version: .*/version: ${APP_VERSION}+${BUILD_NUMBER}/" "$PUBSPEC"
79
+
80
+ # Verify the write
81
+ WRITTEN_VERSION=$(grep '^version:' "$PUBSPEC" | head -1)
82
+ echo "Written to pubspec.yaml: $WRITTEN_VERSION"
83
+
84
+ # --- Export ---
85
+ export BUILD_NUMBER
86
+
87
+ if [ -n "${GITHUB_ENV:-}" ]; then
88
+ echo "BUILD_NUMBER=$BUILD_NUMBER" >> "$GITHUB_ENV"
89
+ echo "Exported BUILD_NUMBER to GITHUB_ENV"
90
+ fi
91
+
92
+ echo "Build number set: $BUILD_NUMBER"