pabal-store-api-mcp 1.3.9 → 1.3.11

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.
@@ -7,6 +7,10 @@ interface AppStoreAppInfo {
7
7
  name?: string;
8
8
  supportedLocales?: string[];
9
9
  }
10
+ export declare function resolveAppStoreLocales(allLocales: string[], requestedLocales?: string[]): {
11
+ localesToPush: string[];
12
+ missingLocales: string[];
13
+ };
10
14
  /**
11
15
  * App Store-facing service layer that wraps client creation and common operations.
12
16
  * Keeps MCP tools independent from client factories and SDK details.
@@ -26,12 +30,14 @@ export declare class AppStoreService {
26
30
  updateReleaseNotes(bundleId: string, releaseNotes: Record<string, string>, versionId?: string, supportedLocales?: string[]): Promise<ServiceResult<UpdatedReleaseNotesResult>>;
27
31
  pullReleaseNotes(bundleId: string): Promise<ServiceResult<AppStoreReleaseNote[]>>;
28
32
  createVersion(bundleId: string, versionString: string, autoIncrement?: boolean): Promise<ServiceResult<CreatedAppStoreVersion>>;
29
- pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, uploadImages, slug, }: {
33
+ pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, uploadImages, locales, imageUploadTimeoutMs, slug, }: {
30
34
  config: EnvConfig;
31
35
  bundleId?: string;
32
36
  localAsoData: AsoData;
33
37
  appStoreDataPath: string;
34
38
  uploadImages?: boolean;
39
+ locales?: string[];
40
+ imageUploadTimeoutMs?: number;
35
41
  slug?: string;
36
42
  }): Promise<PushAsoResult>;
37
43
  verifyAuth(expirationSeconds?: number): Promise<VerifyAuthResult<{
@@ -6,6 +6,16 @@ import { verifyAppStoreAuth } from "../../packages/stores/app-store/verify-auth.
6
6
  import { createAppStoreClient } from "../../core/clients/app-store-factory.js";
7
7
  import { parseAppStoreScreenshots, hasScreenshots, APP_STORE_DEVICE_TYPES, } from "../../core/helpers/screenshot-helpers.js";
8
8
  import { checkPushPrerequisites, serviceFailure, toServiceResult, updateRegisteredLocales, } from "./service-helpers.js";
9
+ export function resolveAppStoreLocales(allLocales, requestedLocales) {
10
+ if (!requestedLocales?.length) {
11
+ return { localesToPush: allLocales, missingLocales: [] };
12
+ }
13
+ const requested = new Set(requestedLocales);
14
+ return {
15
+ localesToPush: allLocales.filter((locale) => requested.has(locale)),
16
+ missingLocales: requestedLocales.filter((locale) => !allLocales.includes(locale)),
17
+ };
18
+ }
9
19
  /**
10
20
  * App Store-facing service layer that wraps client creation and common operations.
11
21
  * Keeps MCP tools independent from client factories and SDK details.
@@ -163,7 +173,7 @@ export class AppStoreService {
163
173
  return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_CREATE_VERSION_FAILED, "Failed to create App Store version"));
164
174
  }
165
175
  }
166
- async pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, uploadImages = false, slug, }) {
176
+ async pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, uploadImages = false, locales, imageUploadTimeoutMs, slug, }) {
167
177
  const skip = checkPushPrerequisites({
168
178
  storeLabel: "App Store",
169
179
  configured: Boolean(config.appStore),
@@ -181,9 +191,20 @@ export class AppStoreService {
181
191
  console.error(`[MCP] Bundle ID: ${bundleId}`);
182
192
  try {
183
193
  // Push locale data (supportUrl/marketingUrl already set by prepareAsoDataForPush)
184
- const localesToPush = Object.keys(appStoreData.locales);
194
+ const allLocales = Object.keys(appStoreData.locales);
195
+ const { localesToPush, missingLocales } = resolveAppStoreLocales(allLocales, locales);
196
+ if (missingLocales.length) {
197
+ console.error(`[AppStore] ⚠️ Requested locale(s) not found in local ASO data: ${missingLocales.join(", ")}`);
198
+ }
199
+ if (localesToPush.length === 0) {
200
+ return {
201
+ success: false,
202
+ error: AppError.validation(ERROR_CODES.APP_STORE_ASO_DATA_EMPTY, "No matching App Store locales found to push"),
203
+ };
204
+ }
185
205
  const failedFieldsList = [];
186
- for (const [locale, localeData] of Object.entries(appStoreData.locales)) {
206
+ for (const locale of localesToPush) {
207
+ const localeData = appStoreData.locales[locale];
187
208
  console.error(`[AppStore] 📤 Pushing ${locale}...`);
188
209
  const localeResult = await client.pushAsoData(localeData);
189
210
  if (localeResult.failedFields && localeResult.failedFields.length > 0) {
@@ -279,6 +300,7 @@ export class AppStoreService {
279
300
  const uploadResult = await client.uploadScreenshotsForLocale({
280
301
  locale,
281
302
  screenshots: screenshotsToUpload,
303
+ imageUploadTimeoutMs,
282
304
  });
283
305
  if (uploadResult.failed > 0) {
284
306
  throw new Error(`Screenshot upload reported ${uploadResult.failed} failed files`);
@@ -335,7 +357,6 @@ export class AppStoreService {
335
357
  const version = await client.createNewVersionWithAutoIncrement();
336
358
  const versionId = version.id;
337
359
  const versionString = version.attributes?.versionString ?? "";
338
- const locales = Object.keys(appStoreData.locales);
339
360
  console.error(`[AppStore] ✅ New version ${versionString} created.`);
340
361
  return {
341
362
  success: false,
@@ -344,7 +365,7 @@ export class AppStoreService {
344
365
  versionInfo: {
345
366
  versionId,
346
367
  versionString,
347
- locales,
368
+ locales: Object.keys(appStoreData.locales),
348
369
  },
349
370
  };
350
371
  }
@@ -6,6 +6,10 @@ interface GooglePlayAppInfo {
6
6
  name?: string;
7
7
  supportedLocales?: string[];
8
8
  }
9
+ export declare function resolveGooglePlayLocales(allLocales: string[], requestedLocales?: string[]): {
10
+ localesToPush: string[];
11
+ missingLocales: string[];
12
+ };
9
13
  /**
10
14
  * Google Play-facing service layer that wraps client creation and common operations.
11
15
  * Keeps MCP tools independent from client factories and SDK details.
@@ -21,12 +25,14 @@ export declare class GooglePlayService {
21
25
  updateReleaseNotes(packageName: string, releaseNotes: Record<string, string>, track?: string, supportedLocales?: string[]): Promise<ServiceResult<UpdatedReleaseNotesResult>>;
22
26
  pullReleaseNotes(packageName: string): Promise<ServiceResult<GooglePlayReleaseNote[]>>;
23
27
  createVersion(packageName: string, versionString: string, versionCodes: number[]): Promise<ServiceResult<CreatedGooglePlayVersion>>;
24
- pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, uploadImages, slug, }: {
28
+ pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, uploadImages, locales, imageUploadTimeoutMs, slug, }: {
25
29
  config: EnvConfig;
26
30
  packageName?: string;
27
31
  localAsoData: AsoData;
28
32
  googlePlayDataPath: string;
29
33
  uploadImages?: boolean;
34
+ locales?: string[];
35
+ imageUploadTimeoutMs?: number;
30
36
  slug?: string;
31
37
  }): Promise<PushAsoResult>;
32
38
  verifyAuth(): Promise<VerifyAuthResult<{
@@ -6,6 +6,16 @@ import { verifyPlayStoreAuth } from "../../packages/stores/play-store/verify-aut
6
6
  import { createGooglePlayClient } from "../../core/clients/google-play-factory.js";
7
7
  import { parseGooglePlayScreenshots, hasScreenshots, } from "../../core/helpers/screenshot-helpers.js";
8
8
  import { checkPushPrerequisites, serviceFailure, toServiceResult, updateRegisteredLocales, } from "./service-helpers.js";
9
+ export function resolveGooglePlayLocales(allLocales, requestedLocales) {
10
+ if (!requestedLocales?.length) {
11
+ return { localesToPush: allLocales, missingLocales: [] };
12
+ }
13
+ const requested = new Set(requestedLocales);
14
+ return {
15
+ localesToPush: allLocales.filter((locale) => requested.has(locale)),
16
+ missingLocales: requestedLocales.filter((locale) => !allLocales.includes(locale)),
17
+ };
18
+ }
9
19
  /**
10
20
  * Google Play-facing service layer that wraps client creation and common operations.
11
21
  * Keeps MCP tools independent from client factories and SDK details.
@@ -148,7 +158,7 @@ export class GooglePlayService {
148
158
  return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_CREATE_VERSION_FAILED, "Failed to create Google Play version"));
149
159
  }
150
160
  }
151
- async pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, uploadImages = false, slug, }) {
161
+ async pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, uploadImages = false, locales, imageUploadTimeoutMs, slug, }) {
152
162
  const skip = checkPushPrerequisites({
153
163
  storeLabel: "Google Play",
154
164
  configured: Boolean(config.playStore),
@@ -165,12 +175,28 @@ export class GooglePlayService {
165
175
  console.error(`[MCP] 📤 Pushing to Google Play...`);
166
176
  console.error(`[MCP] Package: ${packageName}`);
167
177
  try {
168
- const localesToPush = Object.keys(googlePlayData.locales);
178
+ const allLocales = Object.keys(googlePlayData.locales);
179
+ const { localesToPush, missingLocales } = resolveGooglePlayLocales(allLocales, locales);
180
+ if (missingLocales.length) {
181
+ console.error(`[GooglePlay] ⚠️ Requested locale(s) not found in local ASO data: ${missingLocales.join(", ")}`);
182
+ }
183
+ if (localesToPush.length === 0) {
184
+ return {
185
+ success: false,
186
+ error: AppError.validation(ERROR_CODES.GOOGLE_PLAY_ASO_DATA_EMPTY, "No matching Google Play locales found to push"),
187
+ };
188
+ }
169
189
  for (const locale of localesToPush) {
170
190
  console.error(`[GooglePlay] 📤 Preparing locale: ${locale}`);
171
191
  }
172
192
  // Push locale data as-is from aso-data.json
173
- await client.pushMultilingualAsoData(googlePlayData);
193
+ await client.pushMultilingualAsoData({
194
+ ...googlePlayData,
195
+ locales: Object.fromEntries(localesToPush.map((locale) => [
196
+ locale,
197
+ googlePlayData.locales[locale],
198
+ ])),
199
+ });
174
200
  // Push app-level contact information
175
201
  if (googlePlayData.contactEmail || googlePlayData.contactWebsite) {
176
202
  console.error(`[GooglePlay] 📤 Pushing app details...`);
@@ -191,14 +217,15 @@ export class GooglePlayService {
191
217
  for (const locale of localesToPush) {
192
218
  try {
193
219
  const localeData = googlePlayData.locales[locale];
194
- // Check if screenshots are defined in aso-data.json
195
- const hasScreenshotsInJson = localeData?.screenshots &&
196
- ((localeData.screenshots.phone &&
197
- localeData.screenshots.phone.length > 0) ||
198
- (localeData.screenshots.tablet &&
199
- localeData.screenshots.tablet.length > 0));
220
+ // Check if images are defined in aso-data.json
221
+ const hasImagesInJson = Boolean(localeData?.featureGraphic) ||
222
+ Boolean(localeData?.screenshots &&
223
+ ((localeData.screenshots.phone &&
224
+ localeData.screenshots.phone.length > 0) ||
225
+ (localeData.screenshots.tablet &&
226
+ localeData.screenshots.tablet.length > 0)));
200
227
  let screenshots;
201
- if (hasScreenshotsInJson) {
228
+ if (hasImagesInJson) {
202
229
  // Use screenshots from aso-data.json (relative paths)
203
230
  console.error(`[GooglePlay] 📋 Using screenshots from aso-data.json for ${locale}`);
204
231
  const relativePaths = localeData.screenshots;
@@ -230,10 +257,17 @@ export class GooglePlayService {
230
257
  featureGraphic: fsScreenshots.featureGraphic,
231
258
  };
232
259
  }
233
- // Google Play requires minimum 2 phone screenshots
260
+ // Google Play requires minimum 2 phone screenshots for that image type.
234
261
  const phoneCount = screenshots.phone.length;
235
- if (phoneCount < 2) {
236
- console.error(`[GooglePlay] ⚠️ Skipping ${locale} - needs at least 2 phone screenshots (found ${phoneCount})`);
262
+ if (phoneCount > 0 && phoneCount < 2) {
263
+ console.error(`[GooglePlay] ⚠️ Skipping phone screenshots for ${locale} - needs at least 2 (found ${phoneCount})`);
264
+ screenshots.phone = [];
265
+ }
266
+ const hasImagesToUpload = screenshots.phone.length > 0 ||
267
+ screenshots.tablet.length > 0 ||
268
+ Boolean(screenshots.featureGraphic);
269
+ if (!hasImagesToUpload) {
270
+ console.error(`[GooglePlay] ⏭️ Skipping ${locale} - no uploadable images found`);
237
271
  skippedLocales.push(locale);
238
272
  continue;
239
273
  }
@@ -247,8 +281,9 @@ export class GooglePlayService {
247
281
  sevenInchScreenshots: screenshots.phone,
248
282
  tenInchScreenshots: screenshots.tablet,
249
283
  featureGraphic: screenshots.featureGraphic || undefined,
284
+ imageUploadTimeoutMs,
250
285
  });
251
- console.error(`[GooglePlay] ✅ Screenshots uploaded for ${locale}: ${uploadResult.uploaded.phoneScreenshots} phone, ${uploadResult.uploaded.sevenInchScreenshots} 7-inch, ${uploadResult.uploaded.tenInchScreenshots} 10-inch`);
286
+ console.error(`[GooglePlay] ✅ Images uploaded for ${locale}: ${uploadResult.uploaded.phoneScreenshots} phone, ${uploadResult.uploaded.sevenInchScreenshots} 7-inch, ${uploadResult.uploaded.tenInchScreenshots} 10-inch, feature graphic ${uploadResult.uploaded.featureGraphic ? "yes" : "no"}`);
252
287
  uploadedLocales.push(locale);
253
288
  }
254
289
  catch (error) {
package/dist/src/index.js CHANGED
@@ -195,6 +195,16 @@ registerToolWithInfo("aso-push", {
195
195
  .boolean()
196
196
  .optional()
197
197
  .describe("Whether to upload images as well"),
198
+ locales: z
199
+ .array(z.string())
200
+ .optional()
201
+ .describe("Optional locale allowlist to push (e.g. ['en-US', 'ko-KR'])"),
202
+ imageUploadTimeoutMs: z
203
+ .number()
204
+ .int()
205
+ .positive()
206
+ .optional()
207
+ .describe("Per-image upload timeout in milliseconds"),
198
208
  dryRun: z
199
209
  .boolean()
200
210
  .optional()
@@ -42,6 +42,7 @@ export declare const ERROR_CODES: {
42
42
  readonly APP_STORE_PULL_RELEASE_NOTES_FAILED: "APP_STORE_PULL_RELEASE_NOTES_FAILED";
43
43
  readonly APP_STORE_CREATE_VERSION_FAILED: "APP_STORE_CREATE_VERSION_FAILED";
44
44
  readonly APP_STORE_PUSH_FAILED: "APP_STORE_PUSH_FAILED";
45
+ readonly APP_STORE_ASO_DATA_EMPTY: "APP_STORE_ASO_DATA_EMPTY";
45
46
  readonly APP_STORE_STATE_ERROR: "APP_STORE_STATE_ERROR";
46
47
  readonly APP_STORE_CREATE_VERSION_FOR_STATE_ERROR_FAILED: "APP_STORE_CREATE_VERSION_FOR_STATE_ERROR_FAILED";
47
48
  readonly APP_STORE_VERIFY_AUTH_FAILED: "APP_STORE_VERIFY_AUTH_FAILED";
@@ -53,6 +54,7 @@ export declare const ERROR_CODES: {
53
54
  readonly GOOGLE_PLAY_PULL_RELEASE_NOTES_FAILED: "GOOGLE_PLAY_PULL_RELEASE_NOTES_FAILED";
54
55
  readonly GOOGLE_PLAY_CREATE_VERSION_FAILED: "GOOGLE_PLAY_CREATE_VERSION_FAILED";
55
56
  readonly GOOGLE_PLAY_PUSH_FAILED: "GOOGLE_PLAY_PUSH_FAILED";
57
+ readonly GOOGLE_PLAY_ASO_DATA_EMPTY: "GOOGLE_PLAY_ASO_DATA_EMPTY";
56
58
  readonly GOOGLE_PLAY_VERIFY_AUTH_FAILED: "GOOGLE_PLAY_VERIFY_AUTH_FAILED";
57
59
  readonly ASO_GOOGLE_PLAY_DATA_PARSE_FAILED: "ASO_GOOGLE_PLAY_DATA_PARSE_FAILED";
58
60
  readonly ASO_APP_STORE_DATA_PARSE_FAILED: "ASO_APP_STORE_DATA_PARSE_FAILED";
@@ -49,6 +49,7 @@ export const ERROR_CODES = {
49
49
  APP_STORE_PULL_RELEASE_NOTES_FAILED: "APP_STORE_PULL_RELEASE_NOTES_FAILED",
50
50
  APP_STORE_CREATE_VERSION_FAILED: "APP_STORE_CREATE_VERSION_FAILED",
51
51
  APP_STORE_PUSH_FAILED: "APP_STORE_PUSH_FAILED",
52
+ APP_STORE_ASO_DATA_EMPTY: "APP_STORE_ASO_DATA_EMPTY",
52
53
  APP_STORE_STATE_ERROR: "APP_STORE_STATE_ERROR",
53
54
  APP_STORE_CREATE_VERSION_FOR_STATE_ERROR_FAILED: "APP_STORE_CREATE_VERSION_FOR_STATE_ERROR_FAILED",
54
55
  APP_STORE_VERIFY_AUTH_FAILED: "APP_STORE_VERIFY_AUTH_FAILED",
@@ -61,6 +62,7 @@ export const ERROR_CODES = {
61
62
  GOOGLE_PLAY_PULL_RELEASE_NOTES_FAILED: "GOOGLE_PLAY_PULL_RELEASE_NOTES_FAILED",
62
63
  GOOGLE_PLAY_CREATE_VERSION_FAILED: "GOOGLE_PLAY_CREATE_VERSION_FAILED",
63
64
  GOOGLE_PLAY_PUSH_FAILED: "GOOGLE_PLAY_PUSH_FAILED",
65
+ GOOGLE_PLAY_ASO_DATA_EMPTY: "GOOGLE_PLAY_ASO_DATA_EMPTY",
64
66
  GOOGLE_PLAY_VERIFY_AUTH_FAILED: "GOOGLE_PLAY_VERIFY_AUTH_FAILED",
65
67
  // ASO data/files
66
68
  ASO_GOOGLE_PLAY_DATA_PARSE_FAILED: "ASO_GOOGLE_PLAY_DATA_PARSE_FAILED",
@@ -82,6 +82,7 @@ export declare class AppStoreClient {
82
82
  imagePath: string;
83
83
  screenshotDisplayType: string;
84
84
  locale: string;
85
+ imageUploadTimeoutMs?: number;
85
86
  }): Promise<void>;
86
87
  /**
87
88
  * Find or create Screenshot Set for a specific display type
@@ -124,6 +125,7 @@ export declare class AppStoreClient {
124
125
  displayType: string;
125
126
  filename: string;
126
127
  }>;
128
+ imageUploadTimeoutMs?: number;
127
129
  }): Promise<{
128
130
  uploaded: number;
129
131
  deleted: number;
@@ -577,7 +577,7 @@ export class AppStoreClient {
577
577
  * 4. Commit upload operation
578
578
  */
579
579
  async uploadScreenshot(options) {
580
- const { imagePath, screenshotDisplayType, locale } = options;
580
+ const { imagePath, screenshotDisplayType, locale, imageUploadTimeoutMs } = options;
581
581
  try {
582
582
  // Get app and version info
583
583
  const appId = await this.findAppId();
@@ -611,7 +611,7 @@ export class AppStoreClient {
611
611
  if (screenshot.uploadOperations &&
612
612
  screenshot.uploadOperations.length > 0) {
613
613
  const uploadOp = screenshot.uploadOperations[0];
614
- await this.uploadFileToUrl(uploadOp.url, fileBuffer, uploadOp.method);
614
+ await this.uploadFileToUrl(uploadOp.url, fileBuffer, uploadOp.method, imageUploadTimeoutMs);
615
615
  }
616
616
  // Step 4: Commit screenshot
617
617
  await this.commitAppScreenshot(screenshot.id);
@@ -703,7 +703,7 @@ export class AppStoreClient {
703
703
  /**
704
704
  * Upload file to reserved URL
705
705
  */
706
- async uploadFileToUrl(url, fileBuffer, method = "PUT") {
706
+ async uploadFileToUrl(url, fileBuffer, method = "PUT", timeoutMs) {
707
707
  const https = await import("node:https");
708
708
  const { URL } = await import("node:url");
709
709
  return new Promise((resolve, reject) => {
@@ -727,6 +727,11 @@ export class AppStoreClient {
727
727
  }
728
728
  });
729
729
  req.on("error", reject);
730
+ if (timeoutMs) {
731
+ req.setTimeout(timeoutMs, () => {
732
+ req.destroy(new Error(`Upload timed out after ${timeoutMs}ms`));
733
+ });
734
+ }
730
735
  req.write(fileBuffer);
731
736
  req.end();
732
737
  });
@@ -853,7 +858,7 @@ export class AppStoreClient {
853
858
  * 3. Upload new screenshots in order
854
859
  */
855
860
  async uploadScreenshotsForLocale(options) {
856
- const { locale, screenshots } = options;
861
+ const { locale, screenshots, imageUploadTimeoutMs } = options;
857
862
  const result = { uploaded: 0, deleted: 0, failed: 0 };
858
863
  if (screenshots.length === 0) {
859
864
  return result;
@@ -914,7 +919,7 @@ export class AppStoreClient {
914
919
  if (screenshotData.uploadOperations &&
915
920
  screenshotData.uploadOperations.length > 0) {
916
921
  const uploadOp = screenshotData.uploadOperations[0];
917
- await this.uploadFileToUrl(uploadOp.url, fileBuffer, uploadOp.method);
922
+ await this.uploadFileToUrl(uploadOp.url, fileBuffer, uploadOp.method, imageUploadTimeoutMs);
918
923
  }
919
924
  // Commit screenshot
920
925
  await this.commitAppScreenshot(screenshotData.id);
@@ -55,6 +55,7 @@ export declare class GooglePlayClient {
55
55
  * Deletes existing screenshots before uploading new ones
56
56
  */
57
57
  uploadScreenshotsForLocale(options: BatchUploadScreenshotsOptions): Promise<BatchUploadScreenshotsResult>;
58
+ private uploadImageWithOptionalTimeout;
58
59
  private getTrack;
59
60
  private updateTrack;
60
61
  private listTracks;
@@ -620,7 +620,7 @@ export class GooglePlayClient {
620
620
  * Deletes existing screenshots before uploading new ones
621
621
  */
622
622
  async uploadScreenshotsForLocale(options) {
623
- const { language, phoneScreenshots = [], sevenInchScreenshots = [], tenInchScreenshots = [], featureGraphic, } = options;
623
+ const { language, phoneScreenshots = [], sevenInchScreenshots = [], tenInchScreenshots = [], featureGraphic, imageUploadTimeoutMs, } = options;
624
624
  const authClient = await this.auth.getClient();
625
625
  const editResponse = await this.createEdit(authClient, this.packageName);
626
626
  const editId = editResponse.data.id;
@@ -694,17 +694,7 @@ export class GooglePlayClient {
694
694
  }
695
695
  const imageBuffer = readFileSync(imagePath);
696
696
  const fileName = imagePath.split("/").pop() || `phone-${i + 1}.png`;
697
- await this.androidPublisher.edits.images.upload({
698
- auth: session.auth,
699
- packageName: session.packageName,
700
- editId: session.editId,
701
- language,
702
- imageType: "phoneScreenshots",
703
- media: {
704
- mimeType: "image/png",
705
- body: imageBuffer,
706
- },
707
- });
697
+ await this.uploadImageWithOptionalTimeout(session, language, "phoneScreenshots", imageBuffer, imageUploadTimeoutMs);
708
698
  console.error(`[GooglePlayClient] ✅ Uploaded ${fileName}`);
709
699
  result.uploaded.phoneScreenshots++;
710
700
  }
@@ -717,17 +707,7 @@ export class GooglePlayClient {
717
707
  }
718
708
  const imageBuffer = readFileSync(imagePath);
719
709
  const fileName = imagePath.split("/").pop() || `tablet7-${i + 1}.png`;
720
- await this.androidPublisher.edits.images.upload({
721
- auth: session.auth,
722
- packageName: session.packageName,
723
- editId: session.editId,
724
- language,
725
- imageType: "sevenInchScreenshots",
726
- media: {
727
- mimeType: "image/png",
728
- body: imageBuffer,
729
- },
730
- });
710
+ await this.uploadImageWithOptionalTimeout(session, language, "sevenInchScreenshots", imageBuffer, imageUploadTimeoutMs);
731
711
  console.error(`[GooglePlayClient] ✅ Uploaded ${fileName}`);
732
712
  result.uploaded.sevenInchScreenshots++;
733
713
  }
@@ -740,34 +720,14 @@ export class GooglePlayClient {
740
720
  }
741
721
  const imageBuffer = readFileSync(imagePath);
742
722
  const fileName = imagePath.split("/").pop() || `tablet10-${i + 1}.png`;
743
- await this.androidPublisher.edits.images.upload({
744
- auth: session.auth,
745
- packageName: session.packageName,
746
- editId: session.editId,
747
- language,
748
- imageType: "tenInchScreenshots",
749
- media: {
750
- mimeType: "image/png",
751
- body: imageBuffer,
752
- },
753
- });
723
+ await this.uploadImageWithOptionalTimeout(session, language, "tenInchScreenshots", imageBuffer, imageUploadTimeoutMs);
754
724
  console.error(`[GooglePlayClient] ✅ Uploaded ${fileName}`);
755
725
  result.uploaded.tenInchScreenshots++;
756
726
  }
757
727
  // Upload feature graphic
758
728
  if (featureGraphic && existsSync(featureGraphic)) {
759
729
  const imageBuffer = readFileSync(featureGraphic);
760
- await this.androidPublisher.edits.images.upload({
761
- auth: session.auth,
762
- packageName: session.packageName,
763
- editId: session.editId,
764
- language,
765
- imageType: "featureGraphic",
766
- media: {
767
- mimeType: "image/png",
768
- body: imageBuffer,
769
- },
770
- });
730
+ await this.uploadImageWithOptionalTimeout(session, language, "featureGraphic", imageBuffer, imageUploadTimeoutMs);
771
731
  console.error(`[GooglePlayClient] ✅ Uploaded feature-graphic.png`);
772
732
  result.uploaded.featureGraphic = true;
773
733
  }
@@ -788,6 +748,19 @@ export class GooglePlayClient {
788
748
  throw error;
789
749
  }
790
750
  }
751
+ async uploadImageWithOptionalTimeout(session, language, imageType, imageBuffer, timeoutMs) {
752
+ await this.androidPublisher.edits.images.upload({
753
+ auth: session.auth,
754
+ packageName: session.packageName,
755
+ editId: session.editId,
756
+ language,
757
+ imageType,
758
+ media: {
759
+ mimeType: "image/png",
760
+ body: imageBuffer,
761
+ },
762
+ }, timeoutMs ? { timeout: timeoutMs } : undefined);
763
+ }
791
764
  async getTrack(session, track) {
792
765
  const response = await this.androidPublisher.edits.tracks.get({
793
766
  auth: session.auth,
@@ -119,6 +119,7 @@ export interface BatchUploadScreenshotsOptions {
119
119
  sevenInchScreenshots?: string[];
120
120
  tenInchScreenshots?: string[];
121
121
  featureGraphic?: string;
122
+ imageUploadTimeoutMs?: number;
122
123
  }
123
124
  /**
124
125
  * Batch Upload Screenshots Result
@@ -5,6 +5,8 @@ interface AsoPushOptions {
5
5
  bundleId?: string;
6
6
  store?: StoreType;
7
7
  uploadImages?: boolean;
8
+ locales?: string[];
9
+ imageUploadTimeoutMs?: number;
8
10
  dryRun?: boolean;
9
11
  }
10
12
  export declare function handleAsoPush(options: AsoPushOptions): Promise<{
@@ -9,7 +9,7 @@ const appResolutionService = new AppResolutionService();
9
9
  const appStoreService = new AppStoreService();
10
10
  const googlePlayService = new GooglePlayService();
11
11
  export async function handleAsoPush(options) {
12
- const { store = "both", uploadImages = false, dryRun = false } = options;
12
+ const { store = "both", uploadImages = false, locales, imageUploadTimeoutMs, dryRun = false, } = options;
13
13
  const resolved = appResolutionService.resolve({
14
14
  slug: options.app,
15
15
  packageName: options.packageName,
@@ -34,6 +34,11 @@ export async function handleAsoPush(options) {
34
34
  if (bundleId)
35
35
  console.error(`[MCP] Bundle ID: ${bundleId}`);
36
36
  console.error(`[MCP] Upload Images: ${uploadImages ? "Yes" : "No"}`);
37
+ if (locales?.length)
38
+ console.error(`[MCP] Locales: ${locales.join(", ")}`);
39
+ if (imageUploadTimeoutMs) {
40
+ console.error(`[MCP] Image Upload Timeout: ${imageUploadTimeoutMs}ms`);
41
+ }
37
42
  console.error(`[MCP] Mode: ${dryRun ? "Dry run" : "Actual push"}`);
38
43
  let config;
39
44
  try {
@@ -136,6 +141,8 @@ export async function handleAsoPush(options) {
136
141
  localAsoData: configData,
137
142
  googlePlayDataPath,
138
143
  uploadImages,
144
+ locales,
145
+ imageUploadTimeoutMs,
139
146
  slug,
140
147
  });
141
148
  results.push(formatPushResult("Google Play", result));
@@ -153,6 +160,8 @@ export async function handleAsoPush(options) {
153
160
  localAsoData: configData,
154
161
  appStoreDataPath,
155
162
  uploadImages,
163
+ locales,
164
+ imageUploadTimeoutMs,
156
165
  slug,
157
166
  });
158
167
  results.push(formatPushResult("App Store", appStoreResult));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-store-api-mcp",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "description": "MCP server for App Store / Play Store ASO workflows",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",