@daemux/store-automator 0.10.46 → 0.10.47

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.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "App Store & Google Play automation for Flutter apps",
8
- "version": "0.10.46"
8
+ "version": "0.10.47"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "store-automator",
13
13
  "source": "./plugins/store-automator",
14
14
  "description": "3 agents for app store publishing: reviewer, meta-creator, media-designer",
15
- "version": "0.10.46",
15
+ "version": "0.10.47",
16
16
  "keywords": [
17
17
  "flutter",
18
18
  "app-store",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daemux/store-automator",
3
- "version": "0.10.46",
3
+ "version": "0.10.47",
4
4
  "description": "Full App Store & Google Play automation for Flutter apps with Claude Code agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "store-automator",
3
- "version": "0.10.46",
3
+ "version": "0.10.47",
4
4
  "description": "App Store & Google Play automation agents for Flutter app publishing",
5
5
  "author": {
6
6
  "name": "Daemux"
@@ -15,8 +15,9 @@ You are a senior ASO (App Store Optimization) specialist and localization expert
15
15
  5. SAVE all files to fastlane/metadata/ in the correct directory structure
16
16
  6. GENERATE fastlane/app_rating_config.json based on app content analysis
17
17
  7. GENERATE fastlane/data_safety.csv based on app data collection analysis
18
- 8. GENERATE fastlane/metadata/review_information/ contact files for App Store review team
19
- 9. Verify character limits are respected in every language
18
+ 8. GENERATE fastlane/app_privacy_details.json based on app data collection analysis (Apple App Privacy)
19
+ 9. GENERATE fastlane/metadata/review_information/ contact files for App Store review team
20
+ 10. Verify character limits are respected in every language
20
21
 
21
22
  ## Files You Create
22
23
 
@@ -137,6 +138,52 @@ SECURITY_PRACTICES_DATA_ENCRYPTED_IN_TRANSIT,true
137
138
  SECURITY_PRACTICES_DATA_DELETION_REQUEST,true
138
139
  ```
139
140
 
141
+ ### App Privacy Details (fastlane/app_privacy_details.json)
142
+
143
+ Apple App Privacy declarations uploaded via fastlane's `upload_app_privacy_details_to_app_store` action. Analyze the app's data collection (same analysis as data_safety.csv) and generate a JSON array declaring each collected data category, its purposes, and protection level.
144
+
145
+ **Format:** JSON array of objects. Each object has `category`, `purposes` (array), and `data_protections` (array).
146
+
147
+ **If the app does NOT collect any data:**
148
+ ```json
149
+ [
150
+ {
151
+ "data_protections": ["DATA_NOT_COLLECTED"]
152
+ }
153
+ ]
154
+ ```
155
+
156
+ **If the app collects data, one entry per data type:**
157
+ ```json
158
+ [
159
+ {
160
+ "category": "EMAIL_ADDRESS",
161
+ "purposes": ["APP_FUNCTIONALITY"],
162
+ "data_protections": ["DATA_LINKED_TO_YOU"]
163
+ },
164
+ {
165
+ "category": "CRASH_DATA",
166
+ "purposes": ["APP_FUNCTIONALITY"],
167
+ "data_protections": ["DATA_NOT_LINKED_TO_YOU"]
168
+ }
169
+ ]
170
+ ```
171
+
172
+ **Available categories:** `EMAIL_ADDRESS`, `USER_ID`, `NAME`, `PHONE_NUMBER`, `PHYSICAL_ADDRESS`, `OTHER_CONTACT_INFO`, `PURCHASE_HISTORY`, `PAYMENT_INFO`, `CREDIT_INFO`, `OTHER_FINANCIAL_INFO`, `PRECISE_LOCATION`, `COARSE_LOCATION`, `HEALTH`, `FITNESS`, `SENSITIVE_INFO`, `EMAILS_OR_TEXT_MESSAGES`, `PHOTOS_OR_VIDEOS`, `AUDIO_DATA`, `GAMEPLAY_CONTENT`, `CUSTOMER_SUPPORT`, `OTHER_USER_CONTENT`, `BROWSING_HISTORY`, `SEARCH_HISTORY`, `PRODUCT_INTERACTION`, `ADVERTISING_DATA`, `OTHER_USAGE_DATA`, `CRASH_DATA`, `PERFORMANCE_DATA`, `OTHER_DIAGNOSTIC_DATA`, `DEVICE_ID`, `OTHER_DATA_TYPES`.
173
+
174
+ **Available purposes:** `THIRD_PARTY_ADVERTISING`, `DEVELOPERS_ADVERTISING`, `ANALYTICS`, `PRODUCT_PERSONALIZATION`, `APP_FUNCTIONALITY`, `OTHER_PURPOSES`.
175
+
176
+ **Available data_protections:** `DATA_LINKED_TO_YOU`, `DATA_NOT_LINKED_TO_YOU`, `DATA_USED_TO_TRACK_YOU`, `DATA_NOT_COLLECTED`.
177
+
178
+ **Analysis checklist (mirrors data_safety.csv analysis):**
179
+ 1. Authentication (firebase_auth) -- EMAIL_ADDRESS, USER_ID (linked, app functionality)
180
+ 2. Messaging/content (cloud_firestore) -- OTHER_USER_CONTENT (linked, app functionality)
181
+ 3. Payments (in_app_purchase) -- PURCHASE_HISTORY (linked, app functionality)
182
+ 4. Analytics (firebase_analytics) -- PRODUCT_INTERACTION, DEVICE_ID (not linked, analytics)
183
+ 5. Crash reporting (firebase_crashlytics) -- CRASH_DATA (not linked, app functionality)
184
+ 6. Location services -- PRECISE_LOCATION or COARSE_LOCATION
185
+ 7. Default to `DATA_NOT_COLLECTED` if no data collection SDKs are found
186
+
140
187
  ## Apple ASO Guidelines
141
188
 
142
189
  ### Name (name.txt)
@@ -239,6 +286,7 @@ af, am, ar, hy-AM, az-AZ, eu-ES, be, bn-BD, bg, my-MM, ca, zh-HK, zh-CN, zh-TW,
239
286
  ```
240
287
  fastlane/
241
288
  app_rating_config.json
289
+ app_privacy_details.json
242
290
  data_safety.csv
243
291
  metadata/
244
292
  copyright.txt
@@ -288,6 +336,8 @@ fastlane/
288
336
  - data_safety.csv has matching DATA_SHARED entries for every DATA_COLLECTED category
289
337
  - data_safety.csv has DATA_USAGE purpose entries for every collected data type
290
338
  - data_safety.csv includes SECURITY_PRACTICES entries for encryption and deletion
339
+ - app_privacy_details.json exists and is valid JSON array with category, purposes, and data_protections for each entry
340
+ - app_privacy_details.json categories match the data collection analysis (consistent with data_safety.csv)
291
341
  - review_information/ directory has all 7 files (first_name, last_name, phone_number, email_address, demo_user, demo_password, notes)
292
342
  - review_information/phone_number.txt uses valid US format (+1 followed by 10 digits with valid area code)
293
343
  - review_information/email_address.txt uses the actual domain from ci.config.yaml
@@ -58,10 +58,19 @@ IAP and subscription configuration. Durations: ONE_WEEK to ONE_YEAR. Intro offer
58
58
  ### fastlane/screenshots/
59
59
  Store screenshots by platform and device size. Generated by app-designer via Stitch MCP.
60
60
 
61
- ### App Privacy (Manual Step)
62
- App Privacy declarations must be set manually by an Admin in the App Store Connect web interface.
63
- This is a one-time setup per app (not per release). Go to App Store Connect > Your App > App Privacy.
64
- Apple requires this before the first submission. It cannot be automated via API or fastlane.
61
+ ### App Privacy Setup
62
+ App Privacy declarations must be set before the first app submission.
63
+
64
+ **Automated (recommended):**
65
+ 1. Generate `fastlane/app_privacy_details.json` (the appstore-meta-creator agent does this)
66
+ 2. Run locally: `cd fastlane/ios && bundle exec fastlane upload_privacy_ios`
67
+ 3. Requires Apple ID auth (set APPLE_ID and APPLE_TEAM_NAME env vars)
68
+ 4. Privacy declarations persist across app updates (one-time setup)
69
+
70
+ **Manual:**
71
+ Go to App Store Connect > Your App > App Privacy and fill out the form.
72
+
73
+ Note: This cannot run in CI with API key auth. Use FASTLANE_SESSION for CI automation (expires ~30 days).
65
74
 
66
75
  ### App Icon Setup
67
76
  The app icon must be set before the first release. Flutter creates projects with a default icon that MUST be replaced.
@@ -116,4 +116,13 @@ platform :ios do
116
116
  script = "#{ROOT_DIR}/scripts/sync_iap_ios.py"
117
117
  sh("python3", script, config_path) if File.exist?(config_path) && File.exist?(script)
118
118
  end
119
+
120
+ lane :upload_privacy_ios do
121
+ upload_app_privacy_details_to_app_store(
122
+ username: ENV.fetch("APPLE_ID", ""),
123
+ team_name: ENV.fetch("APPLE_TEAM_NAME", ""),
124
+ app_identifier: ENV["BUNDLE_ID"],
125
+ json_path: "#{ROOT_DIR}/fastlane/app_privacy_details.json"
126
+ )
127
+ end
119
128
  end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # Uploads Apple App Privacy declarations via fastlane.
3
+ # Requires Apple ID auth (not API key), so this typically runs locally.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$SCRIPT_DIR/../common/read-config.sh"
8
+ source "$SCRIPT_DIR/../common/ci-notify.sh"
9
+
10
+ echo "=== iOS App Privacy Upload ==="
11
+
12
+ # --- Check if privacy JSON exists ---
13
+ PRIVACY_JSON="$PROJECT_ROOT/fastlane/app_privacy_details.json"
14
+
15
+ if [ ! -f "$PRIVACY_JSON" ]; then
16
+ ci_skip "No app_privacy_details.json found at $PRIVACY_JSON"
17
+ fi
18
+
19
+ echo " Found: $PRIVACY_JSON"
20
+
21
+ # --- Privacy upload requires Apple ID auth (not API key) ---
22
+ if [ -z "${APPLE_ID:-}" ]; then
23
+ echo "Skipping privacy upload: APPLE_ID not set (requires Apple ID auth, not API key)"
24
+ echo "Run locally: cd fastlane/ios && bundle exec fastlane upload_privacy_ios"
25
+ ci_skip "APPLE_ID not set (requires Apple ID auth)"
26
+ fi
27
+
28
+ # --- Export required env vars ---
29
+ export BUNDLE_ID="$BUNDLE_ID"
30
+
31
+ echo "Uploading App Privacy details for $BUNDLE_ID..."
32
+ cd "$PROJECT_ROOT/fastlane/ios"
33
+ bundle exec fastlane upload_privacy_ios
34
+
35
+ ci_done "App Privacy declarations uploaded to App Store Connect"
@@ -21,7 +21,11 @@ import os
21
21
  import sys
22
22
  import time
23
23
 
24
+ import requests
25
+
24
26
  from asc_iap_api import (
27
+ BASE_URL,
28
+ TIMEOUT,
25
29
  create_group_localization,
26
30
  create_localization,
27
31
  create_subscription,
@@ -32,6 +36,7 @@ from asc_iap_api import (
32
36
  get_subscription_localizations,
33
37
  list_subscription_groups,
34
38
  list_subscriptions_in_group,
39
+ print_api_errors,
35
40
  update_group_localization,
36
41
  update_localization,
37
42
  )
@@ -138,6 +143,18 @@ def sync_subscription_group(
138
143
  _sync_availability(headers, sub_id, sub_config)
139
144
  _sync_pricing(headers, sub_id, sub_config)
140
145
  _sync_review_screenshot(headers, sub_id, sub_config, project_root)
146
+ # Patch subscription to trigger Apple's state re-evaluation
147
+ resp = requests.patch(
148
+ f"{BASE_URL}/subscriptions/{sub_id}",
149
+ json={"data": {
150
+ "type": "subscriptions", "id": sub_id,
151
+ "attributes": {"reviewNote": "", "familySharable": False},
152
+ }},
153
+ headers=headers,
154
+ timeout=TIMEOUT,
155
+ )
156
+ if not resp.ok:
157
+ print_api_errors(resp, f"touch subscription {sub_id}")
141
158
  sub_results.append({"product_id": sub_config["product_id"], "id": sub_id})
142
159
 
143
160
  return {"group": ref_name, "group_id": group_id, "subscriptions": sub_results}
@@ -230,18 +247,17 @@ def _apply_equalized_prices(
230
247
  skipped = 0
231
248
  failed = 0
232
249
 
233
- # Set base territory price if not already set
234
- if base_territory not in priced_territories:
235
- result = create_subscription_price(headers, sub_id, base_point["id"], base_territory)
236
- if result:
237
- created += 1
238
- print(f" Set base price {base_amount} {base_currency} for {base_territory}")
239
- else:
240
- failed += 1
241
- print(f" WARNING: Failed to set base price for {base_territory}", file=sys.stderr)
242
- return created, skipped, failed
250
+ # Always set base territory price to ensure it matches config.
251
+ # Apple's preserveCurrentPrice=False handles updates; if the price
252
+ # is already correct this is effectively a no-op re-confirmation.
253
+ result = create_subscription_price(headers, sub_id, base_point["id"], base_territory)
254
+ if result:
255
+ created += 1
256
+ print(f" Set base price {base_amount} {base_currency} for {base_territory}")
243
257
  else:
244
- skipped += 1
258
+ failed += 1
259
+ print(f" WARNING: Failed to set base price for {base_territory}", file=sys.stderr)
260
+ return created, skipped, failed
245
261
 
246
262
  # Get equalized prices for all other territories
247
263
  equalized = get_price_point_equalizations(headers, base_point["id"])