@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,91 @@
|
|
|
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 IAP Sync ==="
|
|
9
|
+
|
|
10
|
+
# ── Step 1: Check if IAP config file exists ──
|
|
11
|
+
IAP_CONFIG="$PROJECT_ROOT/fastlane/iap_config.json"
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$IAP_CONFIG" ]; then
|
|
14
|
+
echo "No IAP config found at $IAP_CONFIG. Skipping IAP sync."
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# ── Step 2: Install Fastlane (needed to check plugin availability) ──
|
|
19
|
+
"$COMMON_DIR/install-fastlane.sh" ios
|
|
20
|
+
|
|
21
|
+
# ── Step 3: Check if IAP plugin is available ──
|
|
22
|
+
cd "$APP_ROOT/ios"
|
|
23
|
+
|
|
24
|
+
if ! bundle exec gem list fastlane-plugin-iap --installed >/dev/null 2>&1; then
|
|
25
|
+
echo "WARNING: fastlane-plugin-iap not installed (plugin not yet published)."
|
|
26
|
+
echo "Skipping IAP sync. This is expected until the IAP plugin is released."
|
|
27
|
+
echo "To install when available: add 'fastlane-plugin-iap' to $APP_ROOT/ios/Gemfile"
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "fastlane-plugin-iap is installed. Proceeding with sync."
|
|
32
|
+
|
|
33
|
+
# ── Step 4: Hash-based change detection ──
|
|
34
|
+
CURRENT_HASH=$(shasum -a 256 "$IAP_CONFIG" | cut -d' ' -f1)
|
|
35
|
+
|
|
36
|
+
STATE_DIR="$PROJECT_ROOT/.ci-state"
|
|
37
|
+
mkdir -p "$STATE_DIR"
|
|
38
|
+
STATE_FILE="$STATE_DIR/ios-iap-hash"
|
|
39
|
+
|
|
40
|
+
if [ -f "$STATE_FILE" ]; then
|
|
41
|
+
STORED_HASH=$(cat "$STATE_FILE")
|
|
42
|
+
if [ "$CURRENT_HASH" = "$STORED_HASH" ]; then
|
|
43
|
+
echo "IAP config unchanged (hash: ${CURRENT_HASH:0:12}...). Skipping sync."
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
echo "IAP config changed (old: ${STORED_HASH:0:12}..., new: ${CURRENT_HASH:0:12}...)"
|
|
47
|
+
else
|
|
48
|
+
echo "No cached hash found. First run — will sync IAPs."
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# ── Step 5: Setup Fastlane symlink and build dir ──
|
|
52
|
+
export CM_BUILD_DIR="$PROJECT_ROOT"
|
|
53
|
+
"$COMMON_DIR/link-fastlane.sh" ios
|
|
54
|
+
|
|
55
|
+
# ── Step 6: Set up App Store Connect API key ──
|
|
56
|
+
P8_FULL_PATH="$PROJECT_ROOT/$P8_KEY_PATH"
|
|
57
|
+
if [ ! -f "$P8_FULL_PATH" ]; then
|
|
58
|
+
echo "ERROR: P8 key file not found at $P8_FULL_PATH" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
export APP_STORE_CONNECT_API_KEY_KEY_ID="$APPLE_KEY_ID"
|
|
63
|
+
export APP_STORE_CONNECT_API_KEY_ISSUER_ID="$APPLE_ISSUER_ID"
|
|
64
|
+
export APP_STORE_CONNECT_API_KEY_KEY="$(cat "$P8_FULL_PATH")"
|
|
65
|
+
export APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64="false"
|
|
66
|
+
|
|
67
|
+
echo "ASC API key configured (Key ID: $APPLE_KEY_ID)"
|
|
68
|
+
|
|
69
|
+
# ── Step 7: Run IAP sync ──
|
|
70
|
+
echo "Syncing IAPs to App Store Connect..."
|
|
71
|
+
|
|
72
|
+
cd "$APP_ROOT/ios"
|
|
73
|
+
|
|
74
|
+
set +e
|
|
75
|
+
FASTLANE_API_KEY_PATH="$P8_FULL_PATH" \
|
|
76
|
+
BUNDLE_ID="$BUNDLE_ID" \
|
|
77
|
+
bundle exec fastlane sync_iap
|
|
78
|
+
SYNC_EXIT=$?
|
|
79
|
+
set -e
|
|
80
|
+
|
|
81
|
+
# ── Step 8: Update hash on success ──
|
|
82
|
+
if [ $SYNC_EXIT -eq 0 ]; then
|
|
83
|
+
echo "$CURRENT_HASH" > "$STATE_FILE"
|
|
84
|
+
echo "IAP sync successful. Hash cached: ${CURRENT_HASH:0:12}..."
|
|
85
|
+
else
|
|
86
|
+
echo "ERROR: IAP sync failed (exit code: $SYNC_EXIT)" >&2
|
|
87
|
+
echo "Hash NOT cached — next run will retry."
|
|
88
|
+
exit $SYNC_EXIT
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
echo "=== iOS IAP Sync Complete ==="
|
|
@@ -0,0 +1,44 @@
|
|
|
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 "$BUNDLE_ID" ]; then
|
|
9
|
+
echo "ERROR: BUNDLE_ID not set in ci.config.yaml" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [ -z "$P8_KEY_PATH" ] || [ -z "$APPLE_KEY_ID" ] || [ -z "$APPLE_ISSUER_ID" ]; then
|
|
14
|
+
echo "ERROR: Apple credentials not configured in ci.config.yaml" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
P8_FULL_PATH="$PROJECT_ROOT/$P8_KEY_PATH"
|
|
19
|
+
if [ ! -f "$P8_FULL_PATH" ]; then
|
|
20
|
+
echo "ERROR: P8 key file not found at $P8_FULL_PATH" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# --- Setup Fastlane ---
|
|
25
|
+
export CM_BUILD_DIR="$PROJECT_ROOT"
|
|
26
|
+
"$SCRIPT_DIR/../common/link-fastlane.sh" ios
|
|
27
|
+
"$SCRIPT_DIR/../common/install-fastlane.sh" ios
|
|
28
|
+
|
|
29
|
+
# --- Set up Fastlane environment ---
|
|
30
|
+
export FASTLANE_API_KEY_PATH="$P8_FULL_PATH"
|
|
31
|
+
export APP_STORE_CONNECT_KEY_IDENTIFIER="$APPLE_KEY_ID"
|
|
32
|
+
export APP_STORE_CONNECT_ISSUER_ID="$APPLE_ISSUER_ID"
|
|
33
|
+
export APP_STORE_CONNECT_PRIVATE_KEY
|
|
34
|
+
APP_STORE_CONNECT_PRIVATE_KEY=$(cat "$P8_FULL_PATH")
|
|
35
|
+
|
|
36
|
+
echo "ASC API key configured (Key ID: $APPLE_KEY_ID)"
|
|
37
|
+
|
|
38
|
+
# --- Upload via Fastlane ---
|
|
39
|
+
echo "Uploading IPA to App Store Connect..."
|
|
40
|
+
cd "$APP_ROOT/ios"
|
|
41
|
+
|
|
42
|
+
bundle exec fastlane upload_binary_ios
|
|
43
|
+
|
|
44
|
+
echo "iOS binary upload complete"
|
|
@@ -0,0 +1,92 @@
|
|
|
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 Metadata & Screenshots Upload ==="
|
|
9
|
+
|
|
10
|
+
# --- Check preconditions ---
|
|
11
|
+
METADATA_DIR="$PROJECT_ROOT/fastlane/metadata"
|
|
12
|
+
SCREENSHOTS_DIR="$PROJECT_ROOT/fastlane/screenshots/ios"
|
|
13
|
+
|
|
14
|
+
if [ ! -d "$METADATA_DIR" ] && [ ! -d "$SCREENSHOTS_DIR" ]; then
|
|
15
|
+
echo "No metadata or screenshots directories found. Skipping upload."
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
HASH_DIRS=()
|
|
20
|
+
for dir in "$METADATA_DIR" "$SCREENSHOTS_DIR"; do
|
|
21
|
+
[ -d "$dir" ] && HASH_DIRS+=("$dir") && echo " Found: $dir"
|
|
22
|
+
done
|
|
23
|
+
|
|
24
|
+
# --- Hash-based change detection ---
|
|
25
|
+
HASH=$(
|
|
26
|
+
find "${HASH_DIRS[@]}" -type f ! -name '.DS_Store' -print0 \
|
|
27
|
+
| LC_ALL=C sort -z \
|
|
28
|
+
| xargs -0 shasum -a 256 2>/dev/null \
|
|
29
|
+
| shasum -a 256 \
|
|
30
|
+
| cut -d' ' -f1
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
STATE_DIR="$PROJECT_ROOT/.ci-state"
|
|
34
|
+
mkdir -p "$STATE_DIR"
|
|
35
|
+
STATE_FILE="$STATE_DIR/ios-metadata-hash"
|
|
36
|
+
|
|
37
|
+
if [ -f "$STATE_FILE" ]; then
|
|
38
|
+
STORED_HASH=$(cat "$STATE_FILE")
|
|
39
|
+
if [ "$HASH" = "$STORED_HASH" ]; then
|
|
40
|
+
echo "Metadata and screenshots unchanged (hash: ${HASH:0:12}...). Skipping upload."
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
echo "Changes detected (old: ${STORED_HASH:0:12}..., new: ${HASH:0:12}...)"
|
|
44
|
+
else
|
|
45
|
+
echo "No cached hash found. First run — will upload metadata."
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# --- Validate Apple credentials ---
|
|
49
|
+
if [ -z "$P8_KEY_PATH" ] || [ -z "$APPLE_KEY_ID" ] || [ -z "$APPLE_ISSUER_ID" ]; then
|
|
50
|
+
echo "ERROR: Apple credentials not configured in ci.config.yaml" >&2
|
|
51
|
+
exit 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
P8_FULL_PATH="$PROJECT_ROOT/$P8_KEY_PATH"
|
|
55
|
+
if [ ! -f "$P8_FULL_PATH" ]; then
|
|
56
|
+
echo "ERROR: P8 key file not found at $P8_FULL_PATH" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# --- Set up Fastlane environment ---
|
|
61
|
+
export FASTLANE_API_KEY_PATH="$P8_FULL_PATH"
|
|
62
|
+
export APP_STORE_CONNECT_KEY_IDENTIFIER="$APPLE_KEY_ID"
|
|
63
|
+
export APP_STORE_CONNECT_ISSUER_ID="$APPLE_ISSUER_ID"
|
|
64
|
+
export CM_BUILD_DIR="$PROJECT_ROOT"
|
|
65
|
+
export FASTLANE_ENABLE_BETA_DELIVER_SYNC_SCREENSHOTS=1
|
|
66
|
+
|
|
67
|
+
echo "ASC API key configured (Key ID: $APPLE_KEY_ID)"
|
|
68
|
+
|
|
69
|
+
# --- Link and install Fastlane ---
|
|
70
|
+
"$COMMON_DIR/link-fastlane.sh" ios
|
|
71
|
+
"$COMMON_DIR/install-fastlane.sh" ios
|
|
72
|
+
|
|
73
|
+
# --- Run upload ---
|
|
74
|
+
echo "Uploading iOS metadata and screenshots..."
|
|
75
|
+
cd "$APP_ROOT/ios"
|
|
76
|
+
|
|
77
|
+
set +e
|
|
78
|
+
bundle exec fastlane upload_metadata_ios
|
|
79
|
+
UPLOAD_EXIT=$?
|
|
80
|
+
set -e
|
|
81
|
+
|
|
82
|
+
# --- Handle result ---
|
|
83
|
+
if [ $UPLOAD_EXIT -eq 0 ]; then
|
|
84
|
+
echo "$HASH" > "$STATE_FILE"
|
|
85
|
+
echo "iOS metadata uploaded successfully. Hash cached: ${HASH:0:12}..."
|
|
86
|
+
else
|
|
87
|
+
echo "ERROR: iOS metadata upload failed (exit code: $UPLOAD_EXIT)" >&2
|
|
88
|
+
echo "Hash NOT cached — next run will retry."
|
|
89
|
+
exit $UPLOAD_EXIT
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo "=== iOS Metadata & Screenshots Upload Complete ==="
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Update Google Play data safety section from a CSV file.
|
|
4
|
+
|
|
5
|
+
Uses the Google Play Android Developer API v3 via the
|
|
6
|
+
google-api-python-client library with service account authentication.
|
|
7
|
+
|
|
8
|
+
IMPORTANT: The Google Play Developer API has LIMITED support for
|
|
9
|
+
programmatic data safety form updates. The API supports editing the
|
|
10
|
+
data safety form only through the App Content API (appEdits).
|
|
11
|
+
If the API does not support the operation, this script logs the
|
|
12
|
+
limitation and exits gracefully.
|
|
13
|
+
|
|
14
|
+
CSV format:
|
|
15
|
+
question_id,response
|
|
16
|
+
DATA_COLLECTED_PERSONAL_INFO,true
|
|
17
|
+
DATA_SHARED_PERSONAL_INFO,false
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
Environment variables:
|
|
21
|
+
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH - path to service account JSON
|
|
22
|
+
PACKAGE_NAME - Android package name (e.g., com.firstclass.gigachat)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import csv
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def ensure_dependencies():
|
|
33
|
+
"""Install required Python packages if not already present."""
|
|
34
|
+
import subprocess
|
|
35
|
+
subprocess.run(
|
|
36
|
+
["pip3", "install", "google-api-python-client", "google-auth"],
|
|
37
|
+
stdout=subprocess.DEVNULL,
|
|
38
|
+
stderr=subprocess.DEVNULL,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_service_account_credentials(sa_json_path: str):
|
|
43
|
+
"""Load Google service account credentials."""
|
|
44
|
+
try:
|
|
45
|
+
from google.oauth2 import service_account
|
|
46
|
+
from googleapiclient.discovery import build
|
|
47
|
+
|
|
48
|
+
credentials = service_account.Credentials.from_service_account_file(
|
|
49
|
+
sa_json_path,
|
|
50
|
+
scopes=["https://www.googleapis.com/auth/androidpublisher"],
|
|
51
|
+
)
|
|
52
|
+
service = build("androidpublisher", "v3", credentials=credentials)
|
|
53
|
+
return service
|
|
54
|
+
except ImportError:
|
|
55
|
+
print(
|
|
56
|
+
"ERROR: google-api-python-client or google-auth not installed.",
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
59
|
+
print("Install with: pip3 install google-api-python-client google-auth", file=sys.stderr)
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"ERROR: Failed to load service account: {e}", file=sys.stderr)
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_data_safety_csv(csv_path: str) -> dict:
|
|
67
|
+
"""Parse the data safety CSV file into a dictionary."""
|
|
68
|
+
data = {}
|
|
69
|
+
with open(csv_path, "r", encoding="utf-8") as f:
|
|
70
|
+
reader = csv.DictReader(f)
|
|
71
|
+
for row in reader:
|
|
72
|
+
question_id = row.get("question_id", "").strip()
|
|
73
|
+
response = row.get("response", "").strip()
|
|
74
|
+
if question_id:
|
|
75
|
+
data[question_id] = response
|
|
76
|
+
return data
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _cleanup_edit_with_warning(service, package_name: str, edit_id: str, warning: str):
|
|
80
|
+
"""Print a warning and delete an unused edit."""
|
|
81
|
+
print(f"WARNING: {warning}", file=sys.stderr)
|
|
82
|
+
print("Data safety forms must be updated manually via Google Play Console.", file=sys.stderr)
|
|
83
|
+
service.edits().delete(packageName=package_name, editId=edit_id).execute()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _apply_data_safety_edit(service, package_name: str, edit_id: str, data_safety_responses: dict) -> bool:
|
|
87
|
+
"""Apply data safety responses within an existing edit. Returns True on success."""
|
|
88
|
+
try:
|
|
89
|
+
current = (
|
|
90
|
+
service.edits()
|
|
91
|
+
.dataSafety()
|
|
92
|
+
.get(packageName=package_name, editId=edit_id)
|
|
93
|
+
.execute()
|
|
94
|
+
)
|
|
95
|
+
print(f"Current data safety state retrieved: {json.dumps(current, indent=2)}")
|
|
96
|
+
update_body = current.copy()
|
|
97
|
+
for question_id, response in data_safety_responses.items():
|
|
98
|
+
update_body[question_id] = response
|
|
99
|
+
|
|
100
|
+
service.edits().dataSafety().update(
|
|
101
|
+
packageName=package_name,
|
|
102
|
+
editId=edit_id,
|
|
103
|
+
body=update_body,
|
|
104
|
+
).execute()
|
|
105
|
+
print("Data safety form updated")
|
|
106
|
+
|
|
107
|
+
service.edits().commit(
|
|
108
|
+
packageName=package_name, editId=edit_id
|
|
109
|
+
).execute()
|
|
110
|
+
print(f"Edit {edit_id} committed successfully")
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
except AttributeError:
|
|
114
|
+
_cleanup_edit_with_warning(
|
|
115
|
+
service, package_name, edit_id,
|
|
116
|
+
"The dataSafety API endpoint is not available in the current google-api-python-client version.",
|
|
117
|
+
)
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
error_str = str(e)
|
|
122
|
+
if "404" in error_str or "not found" in error_str.lower():
|
|
123
|
+
_cleanup_edit_with_warning(
|
|
124
|
+
service, package_name, edit_id,
|
|
125
|
+
"The dataSafety endpoint returned 404. This API may not be available for this app yet.",
|
|
126
|
+
)
|
|
127
|
+
return False
|
|
128
|
+
raise
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def update_data_safety(
|
|
132
|
+
service, package_name: str, data_safety_responses: dict
|
|
133
|
+
) -> bool:
|
|
134
|
+
"""
|
|
135
|
+
Attempt to update the data safety form via the Google Play API.
|
|
136
|
+
|
|
137
|
+
Creates an edit, delegates the data safety update to _apply_data_safety_edit,
|
|
138
|
+
and handles top-level failures.
|
|
139
|
+
|
|
140
|
+
Returns True on success, False if the API does not support the operation.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
edit_request = service.edits().insert(packageName=package_name, body={})
|
|
144
|
+
edit_response = edit_request.execute()
|
|
145
|
+
edit_id = edit_response["id"]
|
|
146
|
+
print(f"Created edit: {edit_id}")
|
|
147
|
+
|
|
148
|
+
return _apply_data_safety_edit(service, package_name, edit_id, data_safety_responses)
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print(f"ERROR: Failed to update data safety: {e}", file=sys.stderr)
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def validate_inputs():
|
|
156
|
+
"""Validate environment variables and CLI arguments. Returns (sa_json_path, package_name, csv_path)."""
|
|
157
|
+
sa_json_path = os.environ.get("GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH", "")
|
|
158
|
+
package_name = os.environ.get("PACKAGE_NAME", "")
|
|
159
|
+
|
|
160
|
+
if not sa_json_path:
|
|
161
|
+
print("ERROR: GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH not set", file=sys.stderr)
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
if not package_name:
|
|
165
|
+
print("ERROR: PACKAGE_NAME not set", file=sys.stderr)
|
|
166
|
+
sys.exit(1)
|
|
167
|
+
|
|
168
|
+
if len(sys.argv) < 2:
|
|
169
|
+
print(f"Usage: {sys.argv[0]} <data_safety.csv>", file=sys.stderr)
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
|
|
172
|
+
csv_path = sys.argv[1]
|
|
173
|
+
if not Path(csv_path).exists():
|
|
174
|
+
print(f"ERROR: CSV file not found: {csv_path}", file=sys.stderr)
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
|
|
177
|
+
if not Path(sa_json_path).exists():
|
|
178
|
+
print(f"ERROR: Service account JSON not found: {sa_json_path}", file=sys.stderr)
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
return sa_json_path, package_name, csv_path
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def main():
|
|
185
|
+
# --- Ensure dependencies are installed ---
|
|
186
|
+
ensure_dependencies()
|
|
187
|
+
|
|
188
|
+
# --- Validate inputs ---
|
|
189
|
+
sa_json_path, package_name, csv_path = validate_inputs()
|
|
190
|
+
|
|
191
|
+
# --- Parse CSV ---
|
|
192
|
+
data_safety_responses = parse_data_safety_csv(csv_path)
|
|
193
|
+
print(f"Parsed {len(data_safety_responses)} data safety responses from {csv_path}")
|
|
194
|
+
|
|
195
|
+
if not data_safety_responses:
|
|
196
|
+
print("WARNING: No data safety responses found in CSV. Nothing to update.")
|
|
197
|
+
sys.exit(0)
|
|
198
|
+
|
|
199
|
+
# --- Connect to Google Play API ---
|
|
200
|
+
service = load_service_account_credentials(sa_json_path)
|
|
201
|
+
|
|
202
|
+
# --- Attempt update ---
|
|
203
|
+
success = update_data_safety(service, package_name, data_safety_responses)
|
|
204
|
+
|
|
205
|
+
if success:
|
|
206
|
+
print("Data safety update completed successfully")
|
|
207
|
+
result = {"status": "updated", "responses_count": len(data_safety_responses)}
|
|
208
|
+
else:
|
|
209
|
+
print("Data safety update was not applied (API limitation)")
|
|
210
|
+
print("Please update the data safety form manually at:")
|
|
211
|
+
print(f" https://play.google.com/console/developers/app/{package_name}/app-content/data-safety")
|
|
212
|
+
result = {"status": "manual_required", "responses_count": len(data_safety_responses)}
|
|
213
|
+
|
|
214
|
+
print(json.dumps(result))
|
|
215
|
+
# Exit 0 even on API limitation -- this is graceful degradation
|
|
216
|
+
sys.exit(0)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
main()
|