@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +6 -2
- package/bin/cli.mjs +43 -15
- package/package.json +1 -1
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/src/ci-config.mjs +33 -1
- package/src/install-paths.mjs +93 -0
- package/src/install.mjs +39 -78
- package/src/prompt.mjs +31 -7
- package/src/templates.mjs +71 -1
- package/templates/Matchfile.template +8 -0
- package/templates/ci.config.yaml.template +3 -0
- package/templates/codemagic.template.yaml +329 -111
- package/templates/fastlane/android/Fastfile.template +56 -8
- package/templates/fastlane/android/Pluginfile.template +4 -1
- package/templates/fastlane/ios/Fastfile.template +72 -17
- package/templates/fastlane/ios/Pluginfile.template +4 -1
- 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,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"
|