@daemux/store-automator 0.10.26 → 0.10.27

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.26"
8
+ "version": "0.10.27"
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.26",
15
+ "version": "0.10.27",
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.26",
3
+ "version": "0.10.27",
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.26",
3
+ "version": "0.10.27",
4
4
  "description": "App Store & Google Play automation agents for Flutter app publishing",
5
5
  "author": {
6
6
  "name": "Daemux"
@@ -24,10 +24,14 @@ from asc_iap_api import BASE_URL, TIMEOUT, print_api_errors
24
24
  # ---------------------------------------------------------------------------
25
25
 
26
26
  def get_subscription_availability(headers: dict, sub_id: str) -> dict | None:
27
- """Fetch current availability settings for a subscription."""
27
+ """Fetch current availability settings for a subscription.
28
+
29
+ Includes availableTerritories relationship so callers can check territory count.
30
+ """
28
31
  resp = requests.get(
29
32
  f"{BASE_URL}/subscriptions/{sub_id}/subscriptionAvailability",
30
33
  headers=headers,
34
+ params={"include": "availableTerritories", "limit[availableTerritories]": 200},
31
35
  timeout=TIMEOUT,
32
36
  )
33
37
  if not resp.ok:
@@ -35,7 +39,28 @@ def get_subscription_availability(headers: dict, sub_id: str) -> dict | None:
35
39
  return None
36
40
  print_api_errors(resp, f"get availability for subscription {sub_id}")
37
41
  return None
38
- return resp.json().get("data")
42
+ body = resp.json()
43
+ result = body.get("data")
44
+ if result:
45
+ result["_included_territories"] = body.get("included", [])
46
+ return result
47
+
48
+
49
+ def list_all_territory_ids(headers: dict) -> list[str]:
50
+ """Fetch all App Store territory IDs."""
51
+ url: str | None = f"{BASE_URL}/territories"
52
+ params: dict | None = {"limit": 200}
53
+ all_ids: list[str] = []
54
+ while url:
55
+ resp = requests.get(url, headers=headers, params=params, timeout=TIMEOUT)
56
+ if not resp.ok:
57
+ print_api_errors(resp, "list territories")
58
+ return all_ids
59
+ data = resp.json()
60
+ all_ids.extend(t["id"] for t in data.get("data", []))
61
+ url = data.get("links", {}).get("next")
62
+ params = None
63
+ return all_ids
39
64
 
40
65
 
41
66
  def create_subscription_availability(
@@ -40,6 +40,7 @@ from asc_subscription_setup import (
40
40
  get_review_screenshot,
41
41
  get_subscription_availability,
42
42
  get_subscription_prices,
43
+ list_all_territory_ids,
43
44
  reserve_review_screenshot,
44
45
  upload_screenshot_chunks,
45
46
  )
@@ -119,21 +120,36 @@ def sync_subscription_group(
119
120
 
120
121
 
121
122
  def _sync_availability(headers: dict, sub_id: str, sub_config: dict) -> None:
122
- """Ensure subscription territory availability is configured."""
123
+ """Ensure subscription territory availability is configured with all territories.
124
+
125
+ If no territories are specified in config, fetches all App Store territories
126
+ so the subscription is available everywhere (empty list = removed from sale).
127
+ Also re-creates availability if existing territory count is suspiciously low.
128
+ """
123
129
  avail_config = sub_config.get("availability", {})
124
130
  available_in_new = avail_config.get("available_in_new_territories", True)
125
131
  territory_ids = avail_config.get("territories", [])
126
132
 
133
+ # If no territories specified, fetch all to make available everywhere
134
+ if not territory_ids:
135
+ territory_ids = list_all_territory_ids(headers)
136
+ if not territory_ids:
137
+ print(" WARNING: Could not fetch territories", file=sys.stderr)
138
+ return
139
+
127
140
  existing = get_subscription_availability(headers, sub_id)
128
141
  if existing:
129
- print(" Subscription availability already configured")
130
- return
142
+ included = existing.get("_included_territories", [])
143
+ if len(included) >= 100:
144
+ print(f" Availability OK ({len(included)} territories)")
145
+ return
146
+ print(f" Availability has only {len(included)} territories, recreating with {len(territory_ids)}")
131
147
 
132
148
  result = create_subscription_availability(
133
149
  headers, sub_id, territory_ids, available_in_new=available_in_new,
134
150
  )
135
151
  if result:
136
- print(" Subscription availability configured successfully")
152
+ print(f" Availability set ({len(territory_ids)} territories)")
137
153
  else:
138
154
  print(" WARNING: Failed to configure availability", file=sys.stderr)
139
155
 
@@ -178,16 +194,38 @@ def _sync_pricing(headers: dict, sub_id: str, sub_config: dict) -> None:
178
194
  def _sync_review_screenshot(
179
195
  headers: dict, sub_id: str, sub_config: dict, project_root: str,
180
196
  ) -> None:
181
- """Upload a review screenshot for the subscription if configured."""
197
+ """Upload a review screenshot for the subscription if configured.
198
+
199
+ Falls back to the first iPhone screenshot from fastlane/screenshots/ios/en-US/
200
+ when the configured path does not exist.
201
+ """
182
202
  screenshot_path = sub_config.get("review_screenshot")
183
203
  if not screenshot_path:
184
204
  print(" WARNING: No review_screenshot configured, skipping", file=sys.stderr)
185
205
  return
186
206
 
187
207
  full_path = os.path.join(project_root, screenshot_path)
208
+
209
+ # Fallback: if configured path doesn't exist, pick first iPhone screenshot
188
210
  if not os.path.isfile(full_path):
189
- print(f" WARNING: Screenshot not found: {full_path}", file=sys.stderr)
190
- return
211
+ fallback = None
212
+ ios_dir = os.path.join(project_root, "fastlane", "screenshots", "ios", "en-US")
213
+ if os.path.isdir(ios_dir):
214
+ for f in sorted(os.listdir(ios_dir)):
215
+ if f.lower().endswith(".png") and "iphone" in f.lower():
216
+ fallback = os.path.join(ios_dir, f)
217
+ break
218
+ if not fallback:
219
+ for f in sorted(os.listdir(ios_dir)):
220
+ if f.lower().endswith(".png"):
221
+ fallback = os.path.join(ios_dir, f)
222
+ break
223
+ if fallback:
224
+ full_path = fallback
225
+ print(f" Using fallback screenshot: {os.path.basename(full_path)}")
226
+ else:
227
+ print(f" WARNING: Screenshot not found: {full_path}", file=sys.stderr)
228
+ return
191
229
 
192
230
  existing = get_review_screenshot(headers, sub_id)
193
231
  if existing: