@daemux/store-automator 0.10.26 → 0.10.28

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.28"
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.28",
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.28",
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.28",
4
4
  "description": "App Store & Google Play automation agents for Flutter app publishing",
5
5
  "author": {
6
6
  "name": "Daemux"
@@ -24,7 +24,11 @@ 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
+ Returns the availability resource or None if not yet configured.
30
+ Does not include territory details to avoid ASC API limit errors.
31
+ """
28
32
  resp = requests.get(
29
33
  f"{BASE_URL}/subscriptions/{sub_id}/subscriptionAvailability",
30
34
  headers=headers,
@@ -38,6 +42,23 @@ def get_subscription_availability(headers: dict, sub_id: str) -> dict | None:
38
42
  return resp.json().get("data")
39
43
 
40
44
 
45
+ def list_all_territory_ids(headers: dict) -> list[str]:
46
+ """Fetch all App Store territory IDs."""
47
+ url: str | None = f"{BASE_URL}/territories"
48
+ params: dict | None = {"limit": 200}
49
+ all_ids: list[str] = []
50
+ while url:
51
+ resp = requests.get(url, headers=headers, params=params, timeout=TIMEOUT)
52
+ if not resp.ok:
53
+ print_api_errors(resp, "list territories")
54
+ return all_ids
55
+ data = resp.json()
56
+ all_ids.extend(t["id"] for t in data.get("data", []))
57
+ url = data.get("links", {}).get("next")
58
+ params = None
59
+ return all_ids
60
+
61
+
41
62
  def create_subscription_availability(
42
63
  headers: dict,
43
64
  sub_id: str,
@@ -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,32 @@ 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
- avail_config = sub_config.get("availability", {})
124
- available_in_new = avail_config.get("available_in_new_territories", True)
125
- territory_ids = avail_config.get("territories", [])
123
+ """Ensure subscription territory availability is configured with all territories.
126
124
 
125
+ If availability already exists, it is left as-is (idempotent skip).
126
+ Otherwise fetches all App Store territories and creates availability.
127
+ """
127
128
  existing = get_subscription_availability(headers, sub_id)
128
129
  if existing:
129
- print(" Subscription availability already configured")
130
+ print(" Availability already configured")
130
131
  return
131
132
 
133
+ avail_config = sub_config.get("availability", {})
134
+ available_in_new = avail_config.get("available_in_new_territories", True)
135
+ territory_ids = avail_config.get("territories", [])
136
+
137
+ # If no territories specified, fetch all to make available everywhere
138
+ if not territory_ids:
139
+ territory_ids = list_all_territory_ids(headers)
140
+ if not territory_ids:
141
+ print(" WARNING: Could not fetch territories", file=sys.stderr)
142
+ return
143
+
132
144
  result = create_subscription_availability(
133
145
  headers, sub_id, territory_ids, available_in_new=available_in_new,
134
146
  )
135
147
  if result:
136
- print(" Subscription availability configured successfully")
148
+ print(f" Availability set ({len(territory_ids)} territories)")
137
149
  else:
138
150
  print(" WARNING: Failed to configure availability", file=sys.stderr)
139
151
 
@@ -178,16 +190,38 @@ def _sync_pricing(headers: dict, sub_id: str, sub_config: dict) -> None:
178
190
  def _sync_review_screenshot(
179
191
  headers: dict, sub_id: str, sub_config: dict, project_root: str,
180
192
  ) -> None:
181
- """Upload a review screenshot for the subscription if configured."""
193
+ """Upload a review screenshot for the subscription if configured.
194
+
195
+ Falls back to the first iPhone screenshot from fastlane/screenshots/ios/en-US/
196
+ when the configured path does not exist.
197
+ """
182
198
  screenshot_path = sub_config.get("review_screenshot")
183
199
  if not screenshot_path:
184
200
  print(" WARNING: No review_screenshot configured, skipping", file=sys.stderr)
185
201
  return
186
202
 
187
203
  full_path = os.path.join(project_root, screenshot_path)
204
+
205
+ # Fallback: if configured path doesn't exist, pick first iPhone screenshot
188
206
  if not os.path.isfile(full_path):
189
- print(f" WARNING: Screenshot not found: {full_path}", file=sys.stderr)
190
- return
207
+ fallback = None
208
+ ios_dir = os.path.join(project_root, "fastlane", "screenshots", "ios", "en-US")
209
+ if os.path.isdir(ios_dir):
210
+ for f in sorted(os.listdir(ios_dir)):
211
+ if f.lower().endswith(".png") and "iphone" in f.lower():
212
+ fallback = os.path.join(ios_dir, f)
213
+ break
214
+ if not fallback:
215
+ for f in sorted(os.listdir(ios_dir)):
216
+ if f.lower().endswith(".png"):
217
+ fallback = os.path.join(ios_dir, f)
218
+ break
219
+ if fallback:
220
+ full_path = fallback
221
+ print(f" Using fallback screenshot: {os.path.basename(full_path)}")
222
+ else:
223
+ print(f" WARNING: Screenshot not found: {full_path}", file=sys.stderr)
224
+ return
191
225
 
192
226
  existing = get_review_screenshot(headers, sub_id)
193
227
  if existing: