pabal-store-api-mcp 1.3.0 → 1.3.1

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.
@@ -201,41 +201,71 @@ export class AppStoreService {
201
201
  const { parseAppStoreScreenshots, hasScreenshots } = await import("../../core/helpers/screenshot-helpers.js");
202
202
  const pushDataDir = getAsoPushDir();
203
203
  const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store/app-store/screenshots`;
204
+ const uploadedLocales = [];
205
+ const skippedLocales = [];
206
+ const failedLocales = [];
204
207
  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}`);
208
+ try {
209
+ if (!hasScreenshots(screenshotsBaseDir, locale)) {
210
+ console.error(`[AppStore] ⏭️ Skipping ${locale} - no screenshots directory`);
211
+ skippedLocales.push(locale);
212
+ continue;
213
+ }
214
+ const result = parseAppStoreScreenshots(screenshotsBaseDir, locale);
215
+ // Check if there are any valid screenshots
216
+ const totalScreenshots = Object.values(result.valid).reduce((sum, screenshots) => sum + screenshots.length, 0);
217
+ if (totalScreenshots === 0) {
218
+ console.error(`[AppStore] ⚠️ Skipping ${locale} - no valid screenshots found`);
219
+ skippedLocales.push(locale);
220
+ continue;
221
+ }
222
+ console.error(`[AppStore] 📤 Uploading screenshots for ${locale}...`);
223
+ // Report parsing issues
224
+ if (result.invalid.length > 0) {
225
+ console.error(`[AppStore] ⚠️ Invalid filenames: ${result.invalid.join(", ")}`);
226
+ }
227
+ if (result.unknown.length > 0) {
228
+ console.error(`[AppStore] ⚠️ Unknown device types: ${result.unknown.join(", ")}`);
229
+ }
230
+ // Upload screenshots for each device type
231
+ for (const [displayType, screenshots] of Object.entries(result.valid)) {
232
+ if (screenshots.length === 0)
233
+ continue;
234
+ console.error(`[AppStore] 📱 Uploading ${screenshots.length} screenshots for ${displayType}...`);
235
+ for (const screenshot of screenshots) {
236
+ try {
237
+ await client.uploadScreenshot({
238
+ imagePath: screenshot.path,
239
+ screenshotDisplayType: displayType,
240
+ locale,
241
+ });
242
+ console.error(`[AppStore] ✅ ${screenshot.filename}`);
243
+ }
244
+ catch (uploadError) {
245
+ const msg = uploadError instanceof Error
246
+ ? uploadError.message
247
+ : String(uploadError);
248
+ console.error(`[AppStore] ❌ ${screenshot.filename}: ${msg}`);
249
+ }
235
250
  }
236
251
  }
252
+ uploadedLocales.push(locale);
253
+ console.error(`[AppStore] ✅ Screenshots uploaded for ${locale}`);
254
+ }
255
+ catch (error) {
256
+ console.error(`[AppStore] ❌ Failed to upload screenshots for ${locale}: ${error instanceof Error ? error.message : String(error)}`);
257
+ failedLocales.push(locale);
237
258
  }
238
- console.error(`[AppStore] ✅ Screenshots uploaded for ${locale}`);
259
+ }
260
+ console.error(`[AppStore] 📊 Screenshot upload summary: ${uploadedLocales.length} succeeded, ${skippedLocales.length} skipped, ${failedLocales.length} failed`);
261
+ if (uploadedLocales.length > 0) {
262
+ console.error(`[AppStore] ✅ Uploaded: ${uploadedLocales.join(", ")}`);
263
+ }
264
+ if (skippedLocales.length > 0) {
265
+ console.error(`[AppStore] ⏭️ Skipped: ${skippedLocales.join(", ")}`);
266
+ }
267
+ if (failedLocales.length > 0) {
268
+ console.error(`[AppStore] ❌ Failed: ${failedLocales.join(", ")}`);
239
269
  }
240
270
  }
241
271
  try {
@@ -177,6 +177,7 @@ export class GooglePlayService {
177
177
  });
178
178
  console.error(`[GooglePlay] ✅ App details uploaded successfully`);
179
179
  }
180
+ // Note: YouTube URL is pushed as part of listing data for each locale
180
181
  // Upload screenshots if enabled
181
182
  if (uploadImages && slug) {
182
183
  console.error(`[GooglePlay] 📤 Uploading screenshots...`);
@@ -184,50 +185,80 @@ export class GooglePlayService {
184
185
  const { parseGooglePlayScreenshots, hasScreenshots } = await import("../../core/helpers/screenshot-helpers.js");
185
186
  const pushDataDir = getAsoPushDir();
186
187
  const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store/google-play/screenshots`;
188
+ const uploadedLocales = [];
189
+ const skippedLocales = [];
190
+ const failedLocales = [];
187
191
  for (const locale of localesToPush) {
188
- if (!hasScreenshots(screenshotsBaseDir, locale)) {
189
- console.error(`[GooglePlay] ⏭️ Skipping ${locale} - no screenshots directory`);
190
- continue;
192
+ try {
193
+ if (!hasScreenshots(screenshotsBaseDir, locale)) {
194
+ console.error(`[GooglePlay] ⏭️ Skipping ${locale} - no screenshots directory`);
195
+ skippedLocales.push(locale);
196
+ continue;
197
+ }
198
+ const screenshots = parseGooglePlayScreenshots(screenshotsBaseDir, locale);
199
+ // Google Play requires minimum 2 phone screenshots
200
+ const phoneCount = screenshots.phone.length + screenshots.tablet7.length;
201
+ if (phoneCount < 2) {
202
+ console.error(`[GooglePlay] ⚠️ Skipping ${locale} - needs at least 2 phone/tablet7 screenshots (found ${phoneCount})`);
203
+ skippedLocales.push(locale);
204
+ continue;
205
+ }
206
+ console.error(`[GooglePlay] 📤 Uploading screenshots for ${locale}...`);
207
+ // Upload phone screenshots (phone-*.png)
208
+ for (const imagePath of screenshots.phone) {
209
+ await client.uploadScreenshot({
210
+ imagePath,
211
+ imageType: "phoneScreenshots",
212
+ language: locale,
213
+ });
214
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()}`);
215
+ }
216
+ // Upload 7-inch tablet screenshots as phone
217
+ for (const imagePath of screenshots.tablet7) {
218
+ await client.uploadScreenshot({
219
+ imagePath,
220
+ imageType: "phoneScreenshots",
221
+ language: locale,
222
+ });
223
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()} (as phone)`);
224
+ }
225
+ // Upload 10-inch tablet screenshots as tablet (optional)
226
+ if (screenshots.tablet10.length > 0) {
227
+ for (const imagePath of screenshots.tablet10) {
228
+ await client.uploadScreenshot({
229
+ imagePath,
230
+ imageType: "tenInchScreenshots",
231
+ language: locale,
232
+ });
233
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()} (as tablet)`);
234
+ }
235
+ }
236
+ // Upload feature graphic (optional)
237
+ if (screenshots.featureGraphic) {
238
+ await client.uploadScreenshot({
239
+ imagePath: screenshots.featureGraphic,
240
+ imageType: "featureGraphic",
241
+ language: locale,
242
+ });
243
+ console.error(`[GooglePlay] ✅ feature-graphic.png`);
244
+ }
245
+ uploadedLocales.push(locale);
246
+ console.error(`[GooglePlay] ✅ Screenshots uploaded for ${locale}`);
191
247
  }
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()}`);
248
+ catch (error) {
249
+ console.error(`[GooglePlay] ❌ Failed to upload screenshots for ${locale}: ${error instanceof Error ? error.message : String(error)}`);
250
+ failedLocales.push(locale);
202
251
  }
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}`);
252
+ }
253
+ console.error(`[GooglePlay] 📊 Screenshot upload summary: ${uploadedLocales.length} succeeded, ${skippedLocales.length} skipped, ${failedLocales.length} failed`);
254
+ if (uploadedLocales.length > 0) {
255
+ console.error(`[GooglePlay] ✅ Uploaded: ${uploadedLocales.join(", ")}`);
256
+ }
257
+ if (skippedLocales.length > 0) {
258
+ console.error(`[GooglePlay] ⏭️ Skipped: ${skippedLocales.join(", ")}`);
259
+ }
260
+ if (failedLocales.length > 0) {
261
+ console.error(`[GooglePlay] ❌ Failed: ${failedLocales.join(", ")}`);
231
262
  }
232
263
  }
233
264
  try {
@@ -17,6 +17,7 @@ export interface GooglePlayAsoData {
17
17
  screenshots: GooglePlayScreenshots;
18
18
  featureGraphic?: string;
19
19
  promoGraphic?: string;
20
+ video?: string;
20
21
  category?: string;
21
22
  contentRating?: string;
22
23
  keywords?: string[];
@@ -31,6 +32,7 @@ export interface GooglePlayMultilingualAsoData {
31
32
  defaultLocale?: string;
32
33
  contactEmail?: string;
33
34
  contactWebsite?: string;
35
+ youtubeUrl?: string;
34
36
  }
35
37
  export interface GooglePlayReleaseNote {
36
38
  versionCode: number;
@@ -72,12 +72,18 @@ export function prepareAsoDataForPush(slug, configData, options) {
72
72
  const locales = isGooglePlayMultilingual(googlePlayData)
73
73
  ? googlePlayData.locales
74
74
  : { [googlePlayData.defaultLanguage || DEFAULT_LOCALE]: googlePlayData };
75
+ // Get app-level youtubeUrl if available
76
+ const appLevelYoutubeUrl = isGooglePlayMultilingual(googlePlayData)
77
+ ? googlePlayData.youtubeUrl
78
+ : undefined;
75
79
  const cleanedLocales = {};
76
80
  for (const [locale, localeData] of Object.entries(locales)) {
77
81
  const { screenshots, featureGraphic, ...rest } = localeData;
78
82
  cleanedLocales[locale] = {
79
83
  ...rest,
80
84
  contactWebsite: detailPageUrl,
85
+ // Apply app-level youtubeUrl to all locales if not already set
86
+ video: rest.video || appLevelYoutubeUrl,
81
87
  };
82
88
  }
83
89
  const gpDefaultLocale = isGooglePlayMultilingual(googlePlayData)
@@ -86,6 +92,11 @@ export function prepareAsoDataForPush(slug, configData, options) {
86
92
  storeData.googlePlay = {
87
93
  locales: cleanedLocales,
88
94
  defaultLocale: gpDefaultLocale || DEFAULT_LOCALE,
95
+ contactEmail: isGooglePlayMultilingual(googlePlayData)
96
+ ? googlePlayData.contactEmail
97
+ : undefined,
98
+ contactWebsite: detailPageUrl,
99
+ youtubeUrl: appLevelYoutubeUrl,
89
100
  };
90
101
  }
91
102
  if (configData.appStore) {
@@ -31,6 +31,7 @@ export declare function buildListingRequestBody(data: {
31
31
  title?: string;
32
32
  shortDescription?: string;
33
33
  fullDescription?: string;
34
+ video?: string;
34
35
  }): ListingUpdateAttributes;
35
36
  /**
36
37
  * Build request body for app details update (only defined values)
@@ -121,6 +121,8 @@ export function buildListingRequestBody(data) {
121
121
  body.shortDescription = data.shortDescription;
122
122
  if (data.fullDescription)
123
123
  body.fullDescription = data.fullDescription;
124
+ if (data.video)
125
+ body.video = data.video;
124
126
  return body;
125
127
  }
126
128
  /**
@@ -138,11 +138,15 @@ export class GooglePlayClient {
138
138
  };
139
139
  try {
140
140
  const language = data.defaultLanguage || DEFAULT_LANGUAGE;
141
- if (data.title || data.shortDescription || data.fullDescription) {
141
+ if (data.title ||
142
+ data.shortDescription ||
143
+ data.fullDescription ||
144
+ data.video) {
142
145
  const listingBody = buildListingRequestBody({
143
146
  title: data.title,
144
147
  shortDescription: data.shortDescription,
145
148
  fullDescription: data.fullDescription,
149
+ video: data.video,
146
150
  });
147
151
  console.error(`[GooglePlayClient] Updating listing for ${language}:`, JSON.stringify(listingBody, null, 2));
148
152
  try {
@@ -216,11 +220,13 @@ export class GooglePlayClient {
216
220
  for (const [language, localeData] of Object.entries(data.locales)) {
217
221
  if (localeData.title ||
218
222
  localeData.shortDescription ||
219
- localeData.fullDescription) {
223
+ localeData.fullDescription ||
224
+ localeData.video) {
220
225
  const listingBody = buildListingRequestBody({
221
226
  title: localeData.title,
222
227
  shortDescription: localeData.shortDescription,
223
228
  fullDescription: localeData.fullDescription,
229
+ video: localeData.video,
224
230
  });
225
231
  console.error(`[GooglePlayClient] Updating listing for ${language}...`);
226
232
  try {
@@ -295,6 +301,8 @@ export class GooglePlayClient {
295
301
  console.error(`[GooglePlayClient] No app details to update, skipping`);
296
302
  return;
297
303
  }
304
+ // Note: youtubeUrl is not part of AppDetails API
305
+ // YouTube URLs are managed at listing level (see pushMultilingualAsoData)
298
306
  const authClient = await this.auth.getClient();
299
307
  const editResponse = await this.createEdit(authClient, this.packageName);
300
308
  const editId = editResponse.data.id;
@@ -120,6 +120,7 @@ export interface UpdateReleaseNotesOptions {
120
120
  * App Details Data
121
121
  * Internal type for app details update operations
122
122
  * Based on AppDetails but with optional fields for partial updates
123
+ * Note: YouTube URLs are managed at listing level, not app details level
123
124
  */
124
125
  export interface AppDetailsData {
125
126
  contactEmail?: NonNullable<AppDetails["contactEmail"]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-store-api-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
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",