@daemux/store-automator 0.7.1 → 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.
- package/bin/cli.mjs +38 -14
- package/package.json +1 -1
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/src/ci-config.mjs +21 -0
- package/src/install-paths.mjs +93 -0
- package/src/install.mjs +33 -78
- package/src/templates.mjs +71 -1
- package/templates/Matchfile.template +8 -0
- package/templates/ci.config.yaml.template +3 -0
- package/templates/fastlane/android/Fastfile.template +2 -2
- package/templates/github/workflows/android-release.yml +72 -0
- package/templates/github/workflows/ios-release.yml +62 -0
- package/templates/scripts/ci/android/build.sh +50 -0
- package/templates/scripts/ci/android/check-readiness.sh +99 -0
- package/templates/scripts/ci/android/manage-version.sh +133 -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 +59 -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 +71 -0
- package/templates/scripts/ci/ios/build.sh +52 -0
- package/templates/scripts/ci/ios/manage-version.sh +64 -0
- package/templates/scripts/ci/ios/set-build-number.sh +95 -0
- package/templates/scripts/ci/ios/setup-signing.sh +242 -0
- package/templates/scripts/ci/ios/sync-iap.sh +91 -0
- package/templates/scripts/ci/ios/upload-binary.sh +44 -0
- package/templates/scripts/ci/ios/upload-metadata.sh +92 -0
- package/templates/scripts/update_data_safety.py +220 -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"
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
COMMON_DIR="$SCRIPT_DIR/../common"
|
|
6
|
+
source "$COMMON_DIR/read-config.sh"
|
|
7
|
+
|
|
8
|
+
echo "=== iOS Signing Setup (Fastlane Match) ==="
|
|
9
|
+
|
|
10
|
+
# --- Step 1: Validate config (read-config.sh already exported vars) ---
|
|
11
|
+
validate_config() {
|
|
12
|
+
local match_key="$PROJECT_ROOT/$MATCH_DEPLOY_KEY_PATH"
|
|
13
|
+
if [ ! -f "$match_key" ]; then
|
|
14
|
+
echo "ERROR: Match deploy key not found at $match_key" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ -z "$MATCH_GIT_URL" ]; then
|
|
19
|
+
echo "ERROR: MATCH_GIT_URL not configured in ci.config.yaml" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [ -z "${MATCH_PASSWORD:-}" ]; then
|
|
24
|
+
echo "ERROR: MATCH_PASSWORD env var is required (encryption password for Match repo)" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
local p8_key="$PROJECT_ROOT/$P8_KEY_PATH"
|
|
29
|
+
if [ ! -f "$p8_key" ]; then
|
|
30
|
+
echo "ERROR: P8 key file not found at $p8_key" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# --- Step 2: Set up SSH agent with deploy key ---
|
|
36
|
+
setup_ssh_agent() {
|
|
37
|
+
echo "Setting up SSH agent..."
|
|
38
|
+
local match_key="$PROJECT_ROOT/$MATCH_DEPLOY_KEY_PATH"
|
|
39
|
+
|
|
40
|
+
eval "$(ssh-agent -s)"
|
|
41
|
+
chmod 600 "$match_key"
|
|
42
|
+
ssh-add "$match_key"
|
|
43
|
+
|
|
44
|
+
mkdir -p ~/.ssh
|
|
45
|
+
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
|
|
46
|
+
|
|
47
|
+
echo "SSH agent configured with deploy key"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# --- Step 3: Set up App Store Connect API key ---
|
|
51
|
+
setup_api_key() {
|
|
52
|
+
local p8_full_path="$PROJECT_ROOT/$P8_KEY_PATH"
|
|
53
|
+
|
|
54
|
+
# Env vars for Match and Fastlane built-in api_key resolution
|
|
55
|
+
export APP_STORE_CONNECT_API_KEY_KEY_ID="$APPLE_KEY_ID"
|
|
56
|
+
export APP_STORE_CONNECT_API_KEY_ISSUER_ID="$APPLE_ISSUER_ID"
|
|
57
|
+
export APP_STORE_CONNECT_API_KEY_KEY="$(cat "$p8_full_path")"
|
|
58
|
+
export APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64="false"
|
|
59
|
+
|
|
60
|
+
# Aliases for existing Fastfile helpers
|
|
61
|
+
export APP_STORE_CONNECT_KEY_IDENTIFIER="$APPLE_KEY_ID"
|
|
62
|
+
cp "$p8_full_path" /tmp/AuthKey.p8
|
|
63
|
+
chmod 600 /tmp/AuthKey.p8
|
|
64
|
+
export FASTLANE_API_KEY_PATH="/tmp/AuthKey.p8"
|
|
65
|
+
|
|
66
|
+
echo "App Store Connect API key configured (Key ID: $APPLE_KEY_ID)"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# --- Step 4: Create temporary keychain ---
|
|
70
|
+
KEYCHAIN_NAME="fastlane_tmp"
|
|
71
|
+
KEYCHAIN_DB="$KEYCHAIN_NAME.keychain-db"
|
|
72
|
+
KEYCHAIN_PASSWORD=""
|
|
73
|
+
|
|
74
|
+
setup_keychain() {
|
|
75
|
+
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
|
|
76
|
+
|
|
77
|
+
echo "Creating temporary keychain: $KEYCHAIN_DB"
|
|
78
|
+
|
|
79
|
+
# Remove existing keychain if present (idempotent)
|
|
80
|
+
security delete-keychain "$KEYCHAIN_DB" 2>/dev/null || true
|
|
81
|
+
|
|
82
|
+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_DB"
|
|
83
|
+
security set-keychain-settings -lut 21600 "$KEYCHAIN_DB"
|
|
84
|
+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_DB"
|
|
85
|
+
|
|
86
|
+
# Add to search list while preserving existing keychains
|
|
87
|
+
local existing_keychains
|
|
88
|
+
existing_keychains=$(security list-keychains -d user | tr -d '"' | tr '\n' ' ')
|
|
89
|
+
security list-keychains -d user -s "$KEYCHAIN_DB" $existing_keychains
|
|
90
|
+
security default-keychain -s "$KEYCHAIN_DB"
|
|
91
|
+
|
|
92
|
+
echo "Keychain created and unlocked"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# --- Step 5: Install Fastlane if needed ---
|
|
96
|
+
install_fastlane() {
|
|
97
|
+
"$COMMON_DIR/install-fastlane.sh" ios
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# --- Step 6: Run Fastlane Match ---
|
|
101
|
+
run_match() {
|
|
102
|
+
echo "Running Fastlane Match..."
|
|
103
|
+
cd "$APP_ROOT/ios"
|
|
104
|
+
|
|
105
|
+
MATCH_READONLY="${MATCH_READONLY:-false}"
|
|
106
|
+
|
|
107
|
+
bundle exec fastlane match appstore \
|
|
108
|
+
--git_url "$MATCH_GIT_URL" \
|
|
109
|
+
--app_identifier "$BUNDLE_ID" \
|
|
110
|
+
--keychain_name "$KEYCHAIN_DB" \
|
|
111
|
+
--keychain_password "$KEYCHAIN_PASSWORD" \
|
|
112
|
+
--readonly "$MATCH_READONLY"
|
|
113
|
+
|
|
114
|
+
echo "Match completed successfully"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# --- Step 7: Find provisioning profile UUID ---
|
|
118
|
+
PROFILE_UUID=""
|
|
119
|
+
TEAM_ID=""
|
|
120
|
+
|
|
121
|
+
find_profile_uuid() {
|
|
122
|
+
local profiles_dir="$HOME/Library/MobileDevice/Provisioning Profiles"
|
|
123
|
+
|
|
124
|
+
if [ ! -d "$profiles_dir" ]; then
|
|
125
|
+
echo "ERROR: Provisioning Profiles directory not found" >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
for profile in "$profiles_dir"/*.mobileprovision; do
|
|
130
|
+
[ -f "$profile" ] || continue
|
|
131
|
+
local plist
|
|
132
|
+
plist=$(security cms -D -i "$profile" 2>/dev/null) || continue
|
|
133
|
+
local profile_app_id
|
|
134
|
+
profile_app_id=$(/usr/libexec/PlistBuddy -c "Print :Entitlements:application-identifier" \
|
|
135
|
+
/dev/stdin <<< "$plist" 2>/dev/null || echo "")
|
|
136
|
+
if [[ "$profile_app_id" == *"$BUNDLE_ID" ]]; then
|
|
137
|
+
PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" \
|
|
138
|
+
/dev/stdin <<< "$plist" 2>/dev/null || echo "")
|
|
139
|
+
if [ -n "$PROFILE_UUID" ]; then
|
|
140
|
+
echo "Found provisioning profile: $PROFILE_UUID"
|
|
141
|
+
break
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
done
|
|
145
|
+
|
|
146
|
+
if [ -z "$PROFILE_UUID" ]; then
|
|
147
|
+
echo "ERROR: Could not find provisioning profile for $BUNDLE_ID" >&2
|
|
148
|
+
echo "Installed profiles:" >&2
|
|
149
|
+
ls -la "$profiles_dir/" 2>/dev/null || echo "(directory not found)" >&2
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
find_team_id() {
|
|
155
|
+
# Try extracting from signing identity
|
|
156
|
+
TEAM_ID=$(security find-identity -v -p codesigning "$KEYCHAIN_DB" \
|
|
157
|
+
| grep "Apple Distribution" \
|
|
158
|
+
| head -1 \
|
|
159
|
+
| sed 's/.*(\([A-Z0-9]*\))"/\1/' || echo "")
|
|
160
|
+
|
|
161
|
+
if [ -n "$TEAM_ID" ]; then
|
|
162
|
+
return
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# Fallback: extract from provisioning profile
|
|
166
|
+
local profiles_dir="$HOME/Library/MobileDevice/Provisioning Profiles"
|
|
167
|
+
TEAM_ID=$(/usr/libexec/PlistBuddy -c "Print :TeamIdentifier:0" \
|
|
168
|
+
/dev/stdin <<< "$(security cms -D -i "$profiles_dir/$PROFILE_UUID.mobileprovision" 2>/dev/null)" \
|
|
169
|
+
2>/dev/null || echo "")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# --- Step 8: Generate ExportOptions.plist ---
|
|
173
|
+
generate_export_options() {
|
|
174
|
+
echo "Generating ExportOptions.plist..."
|
|
175
|
+
local export_options="$PROJECT_ROOT/ios_export_options.plist"
|
|
176
|
+
local profile_name="match AppStore $BUNDLE_ID"
|
|
177
|
+
|
|
178
|
+
cat > "$export_options" <<PLIST
|
|
179
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
180
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
181
|
+
<plist version="1.0">
|
|
182
|
+
<dict>
|
|
183
|
+
<key>method</key>
|
|
184
|
+
<string>app-store</string>
|
|
185
|
+
<key>signingStyle</key>
|
|
186
|
+
<string>manual</string>
|
|
187
|
+
<key>teamID</key>
|
|
188
|
+
<string>${TEAM_ID}</string>
|
|
189
|
+
<key>provisioningProfiles</key>
|
|
190
|
+
<dict>
|
|
191
|
+
<key>${BUNDLE_ID}</key>
|
|
192
|
+
<string>${profile_name}</string>
|
|
193
|
+
</dict>
|
|
194
|
+
<key>uploadBitcode</key>
|
|
195
|
+
<false/>
|
|
196
|
+
<key>uploadSymbols</key>
|
|
197
|
+
<true/>
|
|
198
|
+
</dict>
|
|
199
|
+
</plist>
|
|
200
|
+
PLIST
|
|
201
|
+
|
|
202
|
+
echo "ExportOptions.plist written to $export_options"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# --- Step 9: Export outputs ---
|
|
206
|
+
export_outputs() {
|
|
207
|
+
local export_options="$PROJECT_ROOT/ios_export_options.plist"
|
|
208
|
+
local -a outputs=(
|
|
209
|
+
"EXPORT_OPTIONS_PLIST=$export_options"
|
|
210
|
+
"KEYCHAIN_NAME=$KEYCHAIN_DB"
|
|
211
|
+
"PROFILE_UUID=$PROFILE_UUID"
|
|
212
|
+
"TEAM_ID=$TEAM_ID"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if [ -n "${GITHUB_ENV:-}" ]; then
|
|
216
|
+
printf '%s\n' "${outputs[@]}" >> "$GITHUB_ENV"
|
|
217
|
+
echo "Outputs written to GITHUB_ENV"
|
|
218
|
+
else
|
|
219
|
+
echo ""
|
|
220
|
+
echo "=== Local Mode Outputs ==="
|
|
221
|
+
printf '%s\n' "${outputs[@]}"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
echo ""
|
|
225
|
+
echo "=== Signing Verification ==="
|
|
226
|
+
echo "Installed identities:"
|
|
227
|
+
security find-identity -v -p codesigning "$KEYCHAIN_DB"
|
|
228
|
+
echo ""
|
|
229
|
+
echo "iOS signing setup complete"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
# --- Main ---
|
|
233
|
+
validate_config
|
|
234
|
+
setup_ssh_agent
|
|
235
|
+
setup_api_key
|
|
236
|
+
setup_keychain
|
|
237
|
+
install_fastlane
|
|
238
|
+
run_match
|
|
239
|
+
find_profile_uuid
|
|
240
|
+
find_team_id
|
|
241
|
+
generate_export_options
|
|
242
|
+
export_outputs
|