pabal-store-api-mcp 1.2.2 → 1.3.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.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Screenshot upload helper utilities
3
+ * Shared logic for Google Play and App Store screenshot operations
4
+ */
5
+ /**
6
+ * Device type mappings for App Store screenshots
7
+ */
8
+ export declare const APP_STORE_DEVICE_TYPES: {
9
+ readonly iphone67: "APP_IPHONE_67";
10
+ readonly iphone65: "APP_IPHONE_65";
11
+ readonly iphone61: "APP_IPHONE_61";
12
+ readonly iphone58: "APP_IPHONE_58";
13
+ readonly iphone55: "APP_IPHONE_55";
14
+ readonly iphone47: "APP_IPHONE_47";
15
+ readonly iphone40: "APP_IPHONE_40";
16
+ readonly ipadPro129: "APP_IPAD_PRO_3GEN_129";
17
+ readonly ipadPro11: "APP_IPAD_PRO_11";
18
+ readonly ipad105: "APP_IPAD_105";
19
+ readonly ipad97: "APP_IPAD_97";
20
+ };
21
+ export type AppStoreDeviceType = keyof typeof APP_STORE_DEVICE_TYPES;
22
+ export type AppStoreScreenshotDisplayType = (typeof APP_STORE_DEVICE_TYPES)[AppStoreDeviceType];
23
+ /**
24
+ * Screenshot file info parsed from filename
25
+ */
26
+ export interface ParsedScreenshotFile {
27
+ filename: string;
28
+ path: string;
29
+ deviceType: string;
30
+ order: number;
31
+ }
32
+ /**
33
+ * Parse App Store screenshot filename
34
+ * @param filename e.g., "iphone65-1.png"
35
+ * @returns Parsed info or null if invalid
36
+ */
37
+ export declare function parseAppStoreScreenshotFilename(filename: string, basePath: string): ParsedScreenshotFile | null;
38
+ /**
39
+ * Group screenshots by device type with ordering
40
+ */
41
+ export declare function groupScreenshotsByDeviceType(screenshots: ParsedScreenshotFile[]): Record<string, ParsedScreenshotFile[]>;
42
+ /**
43
+ * Get screenshot files for a locale
44
+ * @returns Array of PNG files in the directory, or empty array if directory doesn't exist
45
+ */
46
+ export declare function getLocaleScreenshotFiles(screenshotsBaseDir: string, locale: string): string[];
47
+ /**
48
+ * Check if locale has any screenshot files
49
+ */
50
+ export declare function hasScreenshots(screenshotsBaseDir: string, locale: string): boolean;
51
+ /**
52
+ * Parse and group App Store screenshots for a locale
53
+ */
54
+ export declare function parseAppStoreScreenshots(screenshotsBaseDir: string, locale: string): {
55
+ valid: Record<string, ParsedScreenshotFile[]>;
56
+ invalid: string[];
57
+ unknown: string[];
58
+ };
59
+ /**
60
+ * Parse Google Play screenshot filenames
61
+ * Supports: phone-*.png, tablet7-*.png, tablet10-*.png, feature-graphic.png
62
+ */
63
+ export interface GooglePlayScreenshotFiles {
64
+ phone: string[];
65
+ tablet7: string[];
66
+ tablet10: string[];
67
+ featureGraphic: string | null;
68
+ }
69
+ export declare function parseGooglePlayScreenshots(screenshotsBaseDir: string, locale: string): GooglePlayScreenshotFiles;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Screenshot upload helper utilities
3
+ * Shared logic for Google Play and App Store screenshot operations
4
+ */
5
+ import { existsSync, readdirSync } from "node:fs";
6
+ /**
7
+ * Device type mappings for App Store screenshots
8
+ */
9
+ export const APP_STORE_DEVICE_TYPES = {
10
+ iphone67: "APP_IPHONE_67",
11
+ iphone65: "APP_IPHONE_65",
12
+ iphone61: "APP_IPHONE_61",
13
+ iphone58: "APP_IPHONE_58",
14
+ iphone55: "APP_IPHONE_55",
15
+ iphone47: "APP_IPHONE_47",
16
+ iphone40: "APP_IPHONE_40",
17
+ ipadPro129: "APP_IPAD_PRO_3GEN_129",
18
+ ipadPro11: "APP_IPAD_PRO_11",
19
+ ipad105: "APP_IPAD_105",
20
+ ipad97: "APP_IPAD_97",
21
+ };
22
+ /**
23
+ * Parse App Store screenshot filename
24
+ * @param filename e.g., "iphone65-1.png"
25
+ * @returns Parsed info or null if invalid
26
+ */
27
+ export function parseAppStoreScreenshotFilename(filename, basePath) {
28
+ const match = filename.match(/^([a-zA-Z0-9]+)-(\d+)\.png$/);
29
+ if (!match)
30
+ return null;
31
+ const [, deviceType, orderStr] = match;
32
+ const order = parseInt(orderStr, 10);
33
+ if (!APP_STORE_DEVICE_TYPES[deviceType]) {
34
+ return null;
35
+ }
36
+ return {
37
+ filename,
38
+ path: `${basePath}/${filename}`,
39
+ deviceType: APP_STORE_DEVICE_TYPES[deviceType],
40
+ order,
41
+ };
42
+ }
43
+ /**
44
+ * Group screenshots by device type with ordering
45
+ */
46
+ export function groupScreenshotsByDeviceType(screenshots) {
47
+ const grouped = {};
48
+ for (const screenshot of screenshots) {
49
+ if (!grouped[screenshot.deviceType]) {
50
+ grouped[screenshot.deviceType] = [];
51
+ }
52
+ grouped[screenshot.deviceType].push(screenshot);
53
+ }
54
+ // Sort each group by order
55
+ for (const deviceType in grouped) {
56
+ grouped[deviceType].sort((a, b) => a.order - b.order);
57
+ }
58
+ return grouped;
59
+ }
60
+ /**
61
+ * Get screenshot files for a locale
62
+ * @returns Array of PNG files in the directory, or empty array if directory doesn't exist
63
+ */
64
+ export function getLocaleScreenshotFiles(screenshotsBaseDir, locale) {
65
+ const localeDir = `${screenshotsBaseDir}/${locale}`;
66
+ if (!existsSync(localeDir)) {
67
+ return [];
68
+ }
69
+ return readdirSync(localeDir).filter((file) => file.endsWith(".png"));
70
+ }
71
+ /**
72
+ * Check if locale has any screenshot files
73
+ */
74
+ export function hasScreenshots(screenshotsBaseDir, locale) {
75
+ const files = getLocaleScreenshotFiles(screenshotsBaseDir, locale);
76
+ return files.length > 0;
77
+ }
78
+ /**
79
+ * Parse and group App Store screenshots for a locale
80
+ */
81
+ export function parseAppStoreScreenshots(screenshotsBaseDir, locale) {
82
+ const localeDir = `${screenshotsBaseDir}/${locale}`;
83
+ const files = getLocaleScreenshotFiles(screenshotsBaseDir, locale);
84
+ const validScreenshots = [];
85
+ const invalidFilenames = [];
86
+ const unknownDeviceTypes = [];
87
+ for (const file of files) {
88
+ const parsed = parseAppStoreScreenshotFilename(file, localeDir);
89
+ if (!parsed) {
90
+ invalidFilenames.push(file);
91
+ continue;
92
+ }
93
+ if (!parsed.deviceType) {
94
+ unknownDeviceTypes.push(file);
95
+ continue;
96
+ }
97
+ validScreenshots.push(parsed);
98
+ }
99
+ return {
100
+ valid: groupScreenshotsByDeviceType(validScreenshots),
101
+ invalid: invalidFilenames,
102
+ unknown: unknownDeviceTypes,
103
+ };
104
+ }
105
+ export function parseGooglePlayScreenshots(screenshotsBaseDir, locale) {
106
+ const localeDir = `${screenshotsBaseDir}/${locale}`;
107
+ const files = getLocaleScreenshotFiles(screenshotsBaseDir, locale);
108
+ return {
109
+ phone: files
110
+ .filter((f) => f.startsWith("phone-") && f.endsWith(".png"))
111
+ .map((f) => `${localeDir}/${f}`),
112
+ tablet7: files
113
+ .filter((f) => f.startsWith("tablet7-") && f.endsWith(".png"))
114
+ .map((f) => `${localeDir}/${f}`),
115
+ tablet10: files
116
+ .filter((f) => f.startsWith("tablet10-") && f.endsWith(".png"))
117
+ .map((f) => `${localeDir}/${f}`),
118
+ featureGraphic: files.includes("feature-graphic.png")
119
+ ? `${localeDir}/feature-graphic.png`
120
+ : null,
121
+ };
122
+ }
@@ -27,11 +27,13 @@ export declare class AppStoreService {
27
27
  updateReleaseNotes(bundleId: string, releaseNotes: Record<string, string>, versionId?: string, supportedLocales?: string[]): Promise<ServiceResult<UpdatedReleaseNotesResult>>;
28
28
  pullReleaseNotes(bundleId: string): Promise<ServiceResult<AppStoreReleaseNote[]>>;
29
29
  createVersion(bundleId: string, versionString: string, autoIncrement?: boolean): Promise<ServiceResult<CreatedAppStoreVersion>>;
30
- pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, }: {
30
+ pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, uploadImages, slug, }: {
31
31
  config: EnvConfig;
32
32
  bundleId?: string;
33
33
  localAsoData: PreparedAsoData;
34
34
  appStoreDataPath: string;
35
+ uploadImages?: boolean;
36
+ slug?: string;
35
37
  }): Promise<PushAsoResult>;
36
38
  verifyAuth(expirationSeconds?: number): Promise<VerifyAuthResult<{
37
39
  header: Record<string, unknown>;
@@ -161,7 +161,7 @@ export class AppStoreService {
161
161
  return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_CREATE_VERSION_FAILED, "Failed to create App Store version"));
162
162
  }
163
163
  }
164
- async pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, }) {
164
+ async pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, uploadImages = false, slug, }) {
165
165
  const skip = checkPushPrerequisites({
166
166
  storeLabel: "App Store",
167
167
  configured: Boolean(config.appStore),
@@ -194,6 +194,50 @@ export class AppStoreService {
194
194
  console.error(`[AppStore] ✅ ${locale} uploaded successfully`);
195
195
  }
196
196
  }
197
+ // Upload screenshots if enabled
198
+ if (uploadImages && slug) {
199
+ console.error(`[AppStore] 📤 Uploading screenshots...`);
200
+ const { getAsoPushDir } = await import("../../packages/configs/aso-config/utils.js");
201
+ const { parseAppStoreScreenshots, hasScreenshots } = await import("../../core/helpers/screenshot-helpers.js");
202
+ const pushDataDir = getAsoPushDir();
203
+ const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store/app-store/screenshots`;
204
+ for (const locale of localesToPush) {
205
+ if (!hasScreenshots(screenshotsBaseDir, locale)) {
206
+ console.error(`[AppStore] ⏭️ Skipping ${locale} - no screenshots directory`);
207
+ continue;
208
+ }
209
+ console.error(`[AppStore] 📤 Uploading screenshots for ${locale}...`);
210
+ const result = parseAppStoreScreenshots(screenshotsBaseDir, locale);
211
+ // Report parsing issues
212
+ if (result.invalid.length > 0) {
213
+ console.error(`[AppStore] ⚠️ Invalid filenames: ${result.invalid.join(", ")}`);
214
+ }
215
+ if (result.unknown.length > 0) {
216
+ console.error(`[AppStore] ⚠️ Unknown device types: ${result.unknown.join(", ")}`);
217
+ }
218
+ // Upload screenshots for each device type
219
+ for (const [displayType, screenshots] of Object.entries(result.valid)) {
220
+ console.error(`[AppStore] 📱 Uploading ${screenshots.length} screenshots for ${displayType}...`);
221
+ for (const screenshot of screenshots) {
222
+ try {
223
+ await client.uploadScreenshot({
224
+ imagePath: screenshot.path,
225
+ screenshotDisplayType: displayType,
226
+ locale,
227
+ });
228
+ console.error(`[AppStore] ✅ ${screenshot.filename}`);
229
+ }
230
+ catch (uploadError) {
231
+ const msg = uploadError instanceof Error
232
+ ? uploadError.message
233
+ : String(uploadError);
234
+ console.error(`[AppStore] ❌ ${screenshot.filename}: ${msg}`);
235
+ }
236
+ }
237
+ }
238
+ console.error(`[AppStore] ✅ Screenshots uploaded for ${locale}`);
239
+ }
240
+ }
197
241
  try {
198
242
  const updated = updateRegisteredLocales(ensuredBundleId, "appStore", localesToPush);
199
243
  if (updated) {
@@ -22,11 +22,13 @@ export declare class GooglePlayService {
22
22
  updateReleaseNotes(packageName: string, releaseNotes: Record<string, string>, track?: string, supportedLocales?: string[]): Promise<ServiceResult<UpdatedReleaseNotesResult>>;
23
23
  pullReleaseNotes(packageName: string): Promise<ServiceResult<GooglePlayReleaseNote[]>>;
24
24
  createVersion(packageName: string, versionString: string, versionCodes: number[]): Promise<ServiceResult<CreatedGooglePlayVersion>>;
25
- pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, }: {
25
+ pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, uploadImages, slug, }: {
26
26
  config: EnvConfig;
27
27
  packageName?: string;
28
28
  localAsoData: PreparedAsoData;
29
29
  googlePlayDataPath: string;
30
+ uploadImages?: boolean;
31
+ slug?: string;
30
32
  }): Promise<PushAsoResult>;
31
33
  verifyAuth(): Promise<VerifyAuthResult<{
32
34
  client_email: string;
@@ -146,7 +146,7 @@ export class GooglePlayService {
146
146
  return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_CREATE_VERSION_FAILED, "Failed to create Google Play version"));
147
147
  }
148
148
  }
149
- async pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, }) {
149
+ async pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, uploadImages = false, slug, }) {
150
150
  const skip = checkPushPrerequisites({
151
151
  storeLabel: "Google Play",
152
152
  configured: Boolean(config.playStore),
@@ -168,6 +168,68 @@ export class GooglePlayService {
168
168
  console.error(`[GooglePlay] 📤 Preparing locale: ${locale}`);
169
169
  }
170
170
  await client.pushMultilingualAsoData(googlePlayData);
171
+ // Push app-level contact information
172
+ if (googlePlayData.contactEmail || googlePlayData.contactWebsite) {
173
+ console.error(`[GooglePlay] 📤 Pushing app details...`);
174
+ await client.pushAppDetails({
175
+ contactEmail: googlePlayData.contactEmail,
176
+ contactWebsite: googlePlayData.contactWebsite,
177
+ });
178
+ console.error(`[GooglePlay] ✅ App details uploaded successfully`);
179
+ }
180
+ // Upload screenshots if enabled
181
+ if (uploadImages && slug) {
182
+ console.error(`[GooglePlay] 📤 Uploading screenshots...`);
183
+ const { getAsoPushDir } = await import("../../packages/configs/aso-config/utils.js");
184
+ const { parseGooglePlayScreenshots, hasScreenshots } = await import("../../core/helpers/screenshot-helpers.js");
185
+ const pushDataDir = getAsoPushDir();
186
+ const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store/google-play/screenshots`;
187
+ for (const locale of localesToPush) {
188
+ if (!hasScreenshots(screenshotsBaseDir, locale)) {
189
+ console.error(`[GooglePlay] ⏭️ Skipping ${locale} - no screenshots directory`);
190
+ continue;
191
+ }
192
+ console.error(`[GooglePlay] 📤 Uploading screenshots for ${locale}...`);
193
+ const screenshots = parseGooglePlayScreenshots(screenshotsBaseDir, locale);
194
+ // Upload phone screenshots (phone-*.png)
195
+ for (const imagePath of screenshots.phone) {
196
+ await client.uploadScreenshot({
197
+ imagePath,
198
+ imageType: "phoneScreenshots",
199
+ language: locale,
200
+ });
201
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()}`);
202
+ }
203
+ // Upload 7-inch tablet screenshots as phone
204
+ for (const imagePath of screenshots.tablet7) {
205
+ await client.uploadScreenshot({
206
+ imagePath,
207
+ imageType: "phoneScreenshots",
208
+ language: locale,
209
+ });
210
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()} (as phone)`);
211
+ }
212
+ // Upload 10-inch tablet screenshots as tablet
213
+ for (const imagePath of screenshots.tablet10) {
214
+ await client.uploadScreenshot({
215
+ imagePath,
216
+ imageType: "tenInchScreenshots",
217
+ language: locale,
218
+ });
219
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()} (as tablet)`);
220
+ }
221
+ // Upload feature graphic
222
+ if (screenshots.featureGraphic) {
223
+ await client.uploadScreenshot({
224
+ imagePath: screenshots.featureGraphic,
225
+ imageType: "featureGraphic",
226
+ language: locale,
227
+ });
228
+ console.error(`[GooglePlay] ✅ feature-graphic.png`);
229
+ }
230
+ console.error(`[GooglePlay] ✅ Screenshots uploaded for ${locale}`);
231
+ }
232
+ }
171
233
  try {
172
234
  const updated = updateRegisteredLocales(ensuredPackage, "googlePlay", localesToPush);
173
235
  if (updated) {
@@ -29,6 +29,8 @@ export interface GooglePlayAsoData {
29
29
  export interface GooglePlayMultilingualAsoData {
30
30
  locales: Record<string, GooglePlayAsoData>;
31
31
  defaultLocale?: string;
32
+ contactEmail?: string;
33
+ contactWebsite?: string;
32
34
  }
33
35
  export interface GooglePlayReleaseNote {
34
36
  versionCode: number;
@@ -72,6 +74,11 @@ export interface AppStoreAsoData {
72
74
  export interface AppStoreMultilingualAsoData {
73
75
  locales: Record<string, AppStoreAsoData>;
74
76
  defaultLocale?: string;
77
+ contactEmail?: string;
78
+ supportUrl?: string;
79
+ marketingUrl?: string;
80
+ privacyPolicyUrl?: string;
81
+ termsUrl?: string;
75
82
  }
76
83
  export interface AppStoreReleaseNote {
77
84
  versionString: string;
@@ -70,6 +70,35 @@ export declare class AppStoreClient {
70
70
  private createAppStoreVersionLocalization;
71
71
  private listScreenshotSets;
72
72
  private listScreenshots;
73
+ /**
74
+ * Upload a screenshot to App Store Connect
75
+ * Full implementation with 4-step process:
76
+ * 1. Find or create Screenshot Set for display type and locale
77
+ * 2. Create AppScreenshot with upload operation
78
+ * 3. Upload file to reserved URL
79
+ * 4. Commit upload operation
80
+ */
81
+ uploadScreenshot(options: {
82
+ imagePath: string;
83
+ screenshotDisplayType: string;
84
+ locale: string;
85
+ }): Promise<void>;
86
+ /**
87
+ * Find or create Screenshot Set for a specific display type
88
+ */
89
+ private findOrCreateScreenshotSet;
90
+ /**
91
+ * Create AppScreenshot with upload operation
92
+ */
93
+ private createAppScreenshot;
94
+ /**
95
+ * Upload file to reserved URL
96
+ */
97
+ private uploadFileToUrl;
98
+ /**
99
+ * Commit AppScreenshot after upload
100
+ */
101
+ private commitAppScreenshot;
73
102
  private getApi;
74
103
  private normalizeEndpoint;
75
104
  private requestCollection;
@@ -5,7 +5,7 @@
5
5
  * API Documentation: https://developer.apple.com/documentation/appstoreconnectapi
6
6
  */
7
7
  import { AppStoreConnectAPI } from "appstore-connect-sdk";
8
- import { AppsApi, AppInfosApi, AppInfoLocalizationsApi, AppScreenshotSetsApi, AppStoreVersionLocalizationsApi, AppStoreVersionsApi, ResponseError, } from "appstore-connect-sdk/openapi";
8
+ import { AppsApi, AppInfosApi, AppInfoLocalizationsApi, AppScreenshotSetsApi, AppScreenshotsApi, AppStoreVersionLocalizationsApi, AppStoreVersionsApi, ResponseError, } from "appstore-connect-sdk/openapi";
9
9
  import { DEFAULT_LOCALE } from "../../../packages/configs/aso-config/constants.js";
10
10
  import { convertToAsoData, convertToMultilingualAsoData, convertToReleaseNote, fetchScreenshotsForLocalization, mapLocalizationsByLocale, selectEnglishAppName, sortReleaseNotes, sortVersions, } from "./api-converters.js";
11
11
  import { APP_STORE_API_BASE_URL, APP_STORE_PLATFORM, DEFAULT_APP_LIST_LIMIT, DEFAULT_VERSIONS_FETCH_LIMIT, } from "./constants.js";
@@ -565,6 +565,194 @@ export class AppStoreClient {
565
565
  return await this.handleSdkError(error);
566
566
  }
567
567
  }
568
+ /**
569
+ * Upload a screenshot to App Store Connect
570
+ * Full implementation with 4-step process:
571
+ * 1. Find or create Screenshot Set for display type and locale
572
+ * 2. Create AppScreenshot with upload operation
573
+ * 3. Upload file to reserved URL
574
+ * 4. Commit upload operation
575
+ */
576
+ async uploadScreenshot(options) {
577
+ const { imagePath, screenshotDisplayType, locale } = options;
578
+ const { readFileSync, statSync } = await import("node:fs");
579
+ const { basename } = await import("node:path");
580
+ try {
581
+ // Get app and version info
582
+ const appId = await this.findAppId();
583
+ const versionsResponse = await this.listAppStoreVersions(appId, {
584
+ platform: APP_STORE_PLATFORM,
585
+ limit: DEFAULT_VERSIONS_FETCH_LIMIT,
586
+ });
587
+ const version = sortVersions(versionsResponse.data || [])[0];
588
+ if (!version)
589
+ throw new Error("App Store version not found.");
590
+ // Find localization for this locale
591
+ const localizationsResponse = await this.listAppStoreVersionLocalizations(version.id, locale);
592
+ let localizationId;
593
+ if (localizationsResponse.data?.[0]) {
594
+ localizationId = localizationsResponse.data[0].id;
595
+ }
596
+ else {
597
+ // Create localization if it doesn't exist
598
+ const createResponse = await this.createAppStoreVersionLocalization(version.id, locale, {});
599
+ localizationId = createResponse.data.id;
600
+ }
601
+ // Step 1: Find or create Screenshot Set
602
+ const screenshotSetId = await this.findOrCreateScreenshotSet(localizationId, screenshotDisplayType);
603
+ // Get file info
604
+ const fileBuffer = readFileSync(imagePath);
605
+ const fileSize = statSync(imagePath).size;
606
+ const fileName = basename(imagePath);
607
+ // Step 2: Create AppScreenshot with upload operation
608
+ const screenshot = await this.createAppScreenshot(screenshotSetId, fileName, fileSize);
609
+ // Step 3: Upload file to reserved URL
610
+ if (screenshot.uploadOperations &&
611
+ screenshot.uploadOperations.length > 0) {
612
+ const uploadOp = screenshot.uploadOperations[0];
613
+ await this.uploadFileToUrl(uploadOp.url, fileBuffer, uploadOp.method);
614
+ }
615
+ // Step 4: Commit screenshot
616
+ await this.commitAppScreenshot(screenshot.id);
617
+ }
618
+ catch (error) {
619
+ const msg = error instanceof Error ? error.message : String(error);
620
+ console.error(`[AppStore] ❌ Screenshot upload failed for ${basename(imagePath)}: ${msg}`);
621
+ throw error;
622
+ }
623
+ }
624
+ /**
625
+ * Find or create Screenshot Set for a specific display type
626
+ */
627
+ async findOrCreateScreenshotSet(localizationId, screenshotDisplayType) {
628
+ // List existing screenshot sets
629
+ const setsResponse = await this.listScreenshotSets(localizationId);
630
+ const existingSet = (setsResponse.data || []).find((set) => set.attributes?.screenshotDisplayType === screenshotDisplayType);
631
+ if (existingSet) {
632
+ return existingSet.id;
633
+ }
634
+ // Create new screenshot set
635
+ const appScreenshotSetsApi = await this.getApi(AppScreenshotSetsApi);
636
+ try {
637
+ const response = await appScreenshotSetsApi.appScreenshotSetsCreateInstance({
638
+ appScreenshotSetCreateRequest: {
639
+ data: {
640
+ type: "appScreenshotSets",
641
+ attributes: {
642
+ screenshotDisplayType: screenshotDisplayType,
643
+ },
644
+ relationships: {
645
+ appStoreVersionLocalization: {
646
+ data: {
647
+ type: "appStoreVersionLocalizations",
648
+ id: localizationId,
649
+ },
650
+ },
651
+ },
652
+ },
653
+ },
654
+ });
655
+ return response.data.id;
656
+ }
657
+ catch (error) {
658
+ return await this.handleSdkError(error);
659
+ }
660
+ }
661
+ /**
662
+ * Create AppScreenshot with upload operation
663
+ */
664
+ async createAppScreenshot(screenshotSetId, fileName, fileSize) {
665
+ const appScreenshotsApi = await this.getApi(AppScreenshotsApi);
666
+ try {
667
+ const response = await appScreenshotsApi.appScreenshotsCreateInstance({
668
+ appScreenshotCreateRequest: {
669
+ data: {
670
+ type: "appScreenshots",
671
+ attributes: {
672
+ fileName,
673
+ fileSize,
674
+ },
675
+ relationships: {
676
+ appScreenshotSet: {
677
+ data: {
678
+ type: "appScreenshotSets",
679
+ id: screenshotSetId,
680
+ },
681
+ },
682
+ },
683
+ },
684
+ },
685
+ });
686
+ const screenshot = response.data;
687
+ const uploadOps = screenshot.attributes?.uploadOperations || [];
688
+ return {
689
+ id: screenshot.id,
690
+ uploadOperations: uploadOps.map((op) => ({
691
+ url: op.url,
692
+ method: op.method || "PUT",
693
+ length: op.length,
694
+ offset: op.offset,
695
+ })),
696
+ };
697
+ }
698
+ catch (error) {
699
+ return await this.handleSdkError(error);
700
+ }
701
+ }
702
+ /**
703
+ * Upload file to reserved URL
704
+ */
705
+ async uploadFileToUrl(url, fileBuffer, method = "PUT") {
706
+ const https = await import("node:https");
707
+ const { URL } = await import("node:url");
708
+ return new Promise((resolve, reject) => {
709
+ const parsedUrl = new URL(url);
710
+ const options = {
711
+ hostname: parsedUrl.hostname,
712
+ port: parsedUrl.port,
713
+ path: parsedUrl.pathname + parsedUrl.search,
714
+ method,
715
+ headers: {
716
+ "Content-Type": "image/png",
717
+ "Content-Length": fileBuffer.length,
718
+ },
719
+ };
720
+ const req = https.request(options, (res) => {
721
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
722
+ resolve();
723
+ }
724
+ else {
725
+ reject(new Error(`Upload failed with status ${res.statusCode}`));
726
+ }
727
+ });
728
+ req.on("error", reject);
729
+ req.write(fileBuffer);
730
+ req.end();
731
+ });
732
+ }
733
+ /**
734
+ * Commit AppScreenshot after upload
735
+ */
736
+ async commitAppScreenshot(screenshotId) {
737
+ const appScreenshotsApi = await this.getApi(AppScreenshotsApi);
738
+ try {
739
+ await appScreenshotsApi.appScreenshotsUpdateInstance({
740
+ id: screenshotId,
741
+ appScreenshotUpdateRequest: {
742
+ data: {
743
+ type: "appScreenshots",
744
+ id: screenshotId,
745
+ attributes: {
746
+ uploaded: true,
747
+ },
748
+ },
749
+ },
750
+ });
751
+ }
752
+ catch (error) {
753
+ return await this.handleSdkError(error);
754
+ }
755
+ }
568
756
  async getApi(apiClass) {
569
757
  if (!this.apiCache.has(apiClass)) {
570
758
  this.apiCache.set(apiClass, this.sdk.create(apiClass));
@@ -159,6 +159,8 @@ export async function handleAsoPush(options) {
159
159
  packageName,
160
160
  localAsoData,
161
161
  googlePlayDataPath,
162
+ uploadImages,
163
+ slug,
162
164
  });
163
165
  results.push(formatPushResult("Google Play", result));
164
166
  }
@@ -174,6 +176,8 @@ export async function handleAsoPush(options) {
174
176
  bundleId,
175
177
  localAsoData,
176
178
  appStoreDataPath,
179
+ uploadImages,
180
+ slug,
177
181
  });
178
182
  results.push(formatPushResult("App Store", appStoreResult));
179
183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-store-api-mcp",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
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",