pabal-store-api-mcp 1.3.0 → 1.3.2

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.
@@ -1,8 +1,10 @@
1
1
  import { AppError } from "../../packages/common/errors/app-error.js";
2
2
  import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
3
  import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
4
+ import { getAsoPushDir } from "../../packages/configs/aso-config/utils.js";
4
5
  import { verifyAppStoreAuth } from "../../packages/stores/app-store/verify-auth.js";
5
6
  import { createAppStoreClient } from "../../core/clients/app-store-factory.js";
7
+ import { parseAppStoreScreenshots, hasScreenshots, APP_STORE_DEVICE_TYPES, } from "../../core/helpers/screenshot-helpers.js";
6
8
  import { checkPushPrerequisites, serviceFailure, toServiceResult, updateRegisteredLocales, } from "./service-helpers.js";
7
9
  /**
8
10
  * App Store-facing service layer that wraps client creation and common operations.
@@ -197,32 +199,87 @@ export class AppStoreService {
197
199
  // Upload screenshots if enabled
198
200
  if (uploadImages && slug) {
199
201
  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
202
  const pushDataDir = getAsoPushDir();
203
- const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store/app-store/screenshots`;
203
+ const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store`;
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) {
208
+ try {
209
+ const localeData = appStoreData.locales[locale];
210
+ // Check if screenshots are defined in aso-data.json
211
+ const hasScreenshotsInJson = localeData?.screenshots &&
212
+ ((localeData.screenshots.iphone65 &&
213
+ localeData.screenshots.iphone65.length > 0) ||
214
+ (localeData.screenshots.ipadPro129 &&
215
+ localeData.screenshots.ipadPro129.length > 0));
216
+ let screenshotsToUpload = [];
217
+ if (hasScreenshotsInJson) {
218
+ // Use screenshots from aso-data.json (relative paths)
219
+ console.error(`[AppStore] 📋 Using screenshots from aso-data.json for ${locale}`);
220
+ const relativePaths = localeData.screenshots;
221
+ // Map iphone65 screenshots
222
+ if (relativePaths.iphone65 && relativePaths.iphone65.length > 0) {
223
+ for (const relPath of relativePaths.iphone65) {
224
+ screenshotsToUpload.push({
225
+ path: `${screenshotsBaseDir}/${relPath}`,
226
+ displayType: APP_STORE_DEVICE_TYPES.iphone65,
227
+ filename: relPath.split("/").pop() || relPath,
228
+ });
229
+ }
230
+ }
231
+ // Map ipadPro129 screenshots
232
+ if (relativePaths.ipadPro129 &&
233
+ relativePaths.ipadPro129.length > 0) {
234
+ for (const relPath of relativePaths.ipadPro129) {
235
+ screenshotsToUpload.push({
236
+ path: `${screenshotsBaseDir}/${relPath}`,
237
+ displayType: APP_STORE_DEVICE_TYPES.ipadPro129,
238
+ filename: relPath.split("/").pop() || relPath,
239
+ });
240
+ }
241
+ }
242
+ }
243
+ else {
244
+ // Fallback: Parse from file system (backward compatibility)
245
+ const screenshotsFsDir = `${screenshotsBaseDir}/app-store/screenshots`;
246
+ if (!hasScreenshots(screenshotsFsDir, locale)) {
247
+ console.error(`[AppStore] ⏭️ Skipping ${locale} - no screenshots in aso-data.json or file system`);
248
+ skippedLocales.push(locale);
249
+ continue;
250
+ }
251
+ console.error(`[AppStore] 📂 Parsing screenshots from file system for ${locale}`);
252
+ const result = parseAppStoreScreenshots(screenshotsFsDir, locale);
253
+ // Report parsing issues
254
+ if (result.invalid.length > 0) {
255
+ console.error(`[AppStore] ⚠️ Invalid filenames: ${result.invalid.join(", ")}`);
256
+ }
257
+ if (result.unknown.length > 0) {
258
+ console.error(`[AppStore] ⚠️ Unknown device types: ${result.unknown.join(", ")}`);
259
+ }
260
+ // Convert parsed screenshots to upload format
261
+ for (const [displayType, screenshots] of Object.entries(result.valid)) {
262
+ for (const screenshot of screenshots) {
263
+ screenshotsToUpload.push({
264
+ path: screenshot.path,
265
+ displayType,
266
+ filename: screenshot.filename,
267
+ });
268
+ }
269
+ }
270
+ }
271
+ if (screenshotsToUpload.length === 0) {
272
+ console.error(`[AppStore] ⚠️ Skipping ${locale} - no valid screenshots found`);
273
+ skippedLocales.push(locale);
274
+ continue;
275
+ }
276
+ console.error(`[AppStore] 📤 Uploading screenshots for ${locale}...`);
277
+ // Upload screenshots
278
+ for (const screenshot of screenshotsToUpload) {
222
279
  try {
223
280
  await client.uploadScreenshot({
224
281
  imagePath: screenshot.path,
225
- screenshotDisplayType: displayType,
282
+ screenshotDisplayType: screenshot.displayType,
226
283
  locale,
227
284
  });
228
285
  console.error(`[AppStore] ✅ ${screenshot.filename}`);
@@ -234,8 +291,23 @@ export class AppStoreService {
234
291
  console.error(`[AppStore] ❌ ${screenshot.filename}: ${msg}`);
235
292
  }
236
293
  }
294
+ uploadedLocales.push(locale);
295
+ console.error(`[AppStore] ✅ Screenshots uploaded for ${locale}`);
237
296
  }
238
- console.error(`[AppStore] ✅ Screenshots uploaded for ${locale}`);
297
+ catch (error) {
298
+ console.error(`[AppStore] ❌ Failed to upload screenshots for ${locale}: ${error instanceof Error ? error.message : String(error)}`);
299
+ failedLocales.push(locale);
300
+ }
301
+ }
302
+ console.error(`[AppStore] 📊 Screenshot upload summary: ${uploadedLocales.length} succeeded, ${skippedLocales.length} skipped, ${failedLocales.length} failed`);
303
+ if (uploadedLocales.length > 0) {
304
+ console.error(`[AppStore] ✅ Uploaded: ${uploadedLocales.join(", ")}`);
305
+ }
306
+ if (skippedLocales.length > 0) {
307
+ console.error(`[AppStore] ⏭️ Skipped: ${skippedLocales.join(", ")}`);
308
+ }
309
+ if (failedLocales.length > 0) {
310
+ console.error(`[AppStore] ❌ Failed: ${failedLocales.join(", ")}`);
239
311
  }
240
312
  }
241
313
  try {
@@ -1,8 +1,10 @@
1
1
  import { AppError } from "../../packages/common/errors/app-error.js";
2
2
  import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
3
  import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
4
+ import { getAsoPushDir } from "../../packages/configs/aso-config/utils.js";
4
5
  import { verifyPlayStoreAuth } from "../../packages/stores/play-store/verify-auth.js";
5
6
  import { createGooglePlayClient } from "../../core/clients/google-play-factory.js";
7
+ import { parseGooglePlayScreenshots, hasScreenshots, } from "../../core/helpers/screenshot-helpers.js";
6
8
  import { checkPushPrerequisites, serviceFailure, toServiceResult, updateRegisteredLocales, } from "./service-helpers.js";
7
9
  /**
8
10
  * Google Play-facing service layer that wraps client creation and common operations.
@@ -177,57 +179,114 @@ export class GooglePlayService {
177
179
  });
178
180
  console.error(`[GooglePlay] ✅ App details uploaded successfully`);
179
181
  }
182
+ // Note: YouTube URL is pushed as part of listing data for each locale
180
183
  // Upload screenshots if enabled
181
184
  if (uploadImages && slug) {
182
185
  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
186
  const pushDataDir = getAsoPushDir();
186
- const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store/google-play/screenshots`;
187
+ const screenshotsBaseDir = `${pushDataDir}/products/${slug}/store`;
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
+ 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.tablet7 &&
199
+ localeData.screenshots.tablet7.length > 0) ||
200
+ (localeData.screenshots.tablet10 &&
201
+ localeData.screenshots.tablet10.length > 0));
202
+ let screenshots;
203
+ if (hasScreenshotsInJson) {
204
+ // Use screenshots from aso-data.json (relative paths)
205
+ console.error(`[GooglePlay] 📋 Using screenshots from aso-data.json for ${locale}`);
206
+ const relativePaths = localeData.screenshots;
207
+ screenshots = {
208
+ phone: (relativePaths.phone || []).map((p) => `${screenshotsBaseDir}/${p}`),
209
+ tablet7: (relativePaths.tablet7 || []).map((p) => `${screenshotsBaseDir}/${p}`),
210
+ tablet10: (relativePaths.tablet10 || []).map((p) => `${screenshotsBaseDir}/${p}`),
211
+ featureGraphic: localeData.featureGraphic
212
+ ? `${screenshotsBaseDir}/${localeData.featureGraphic}`
213
+ : null,
214
+ };
215
+ }
216
+ else {
217
+ // Fallback: Parse from file system (backward compatibility)
218
+ const screenshotsFsDir = `${screenshotsBaseDir}/google-play/screenshots`;
219
+ if (!hasScreenshots(screenshotsFsDir, locale)) {
220
+ console.error(`[GooglePlay] ⏭️ Skipping ${locale} - no screenshots in aso-data.json or file system`);
221
+ skippedLocales.push(locale);
222
+ continue;
223
+ }
224
+ console.error(`[GooglePlay] 📂 Parsing screenshots from file system for ${locale}`);
225
+ screenshots = parseGooglePlayScreenshots(screenshotsFsDir, locale);
226
+ }
227
+ // Google Play requires minimum 2 phone screenshots
228
+ const phoneCount = screenshots.phone.length + screenshots.tablet7.length;
229
+ if (phoneCount < 2) {
230
+ console.error(`[GooglePlay] ⚠️ Skipping ${locale} - needs at least 2 phone/tablet7 screenshots (found ${phoneCount})`);
231
+ skippedLocales.push(locale);
232
+ continue;
233
+ }
234
+ console.error(`[GooglePlay] 📤 Uploading screenshots for ${locale}...`);
235
+ // Upload phone screenshots (phone-*.png)
236
+ for (const imagePath of screenshots.phone) {
237
+ await client.uploadScreenshot({
238
+ imagePath,
239
+ imageType: "phoneScreenshots",
240
+ language: locale,
241
+ });
242
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()}`);
243
+ }
244
+ // Upload 7-inch tablet screenshots as phone
245
+ for (const imagePath of screenshots.tablet7) {
246
+ await client.uploadScreenshot({
247
+ imagePath,
248
+ imageType: "phoneScreenshots",
249
+ language: locale,
250
+ });
251
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()} (as phone)`);
252
+ }
253
+ // Upload 10-inch tablet screenshots as tablet (optional)
254
+ if (screenshots.tablet10.length > 0) {
255
+ for (const imagePath of screenshots.tablet10) {
256
+ await client.uploadScreenshot({
257
+ imagePath,
258
+ imageType: "tenInchScreenshots",
259
+ language: locale,
260
+ });
261
+ console.error(`[GooglePlay] ✅ ${imagePath.split("/").pop()} (as tablet)`);
262
+ }
263
+ }
264
+ // Upload feature graphic (optional)
265
+ if (screenshots.featureGraphic) {
266
+ await client.uploadScreenshot({
267
+ imagePath: screenshots.featureGraphic,
268
+ imageType: "featureGraphic",
269
+ language: locale,
270
+ });
271
+ console.error(`[GooglePlay] ✅ feature-graphic.png`);
272
+ }
273
+ uploadedLocales.push(locale);
274
+ console.error(`[GooglePlay] ✅ Screenshots uploaded for ${locale}`);
191
275
  }
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()}`);
276
+ catch (error) {
277
+ console.error(`[GooglePlay] ❌ Failed to upload screenshots for ${locale}: ${error instanceof Error ? error.message : String(error)}`);
278
+ failedLocales.push(locale);
202
279
  }
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}`);
280
+ }
281
+ console.error(`[GooglePlay] 📊 Screenshot upload summary: ${uploadedLocales.length} succeeded, ${skippedLocales.length} skipped, ${failedLocales.length} failed`);
282
+ if (uploadedLocales.length > 0) {
283
+ console.error(`[GooglePlay] ✅ Uploaded: ${uploadedLocales.join(", ")}`);
284
+ }
285
+ if (skippedLocales.length > 0) {
286
+ console.error(`[GooglePlay] ⏭️ Skipped: ${skippedLocales.join(", ")}`);
287
+ }
288
+ if (failedLocales.length > 0) {
289
+ console.error(`[GooglePlay] ❌ Failed: ${failedLocales.join(", ")}`);
231
290
  }
232
291
  }
233
292
  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.2",
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",