pabal-store-api-mcp 1.1.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.
Files changed (111) hide show
  1. package/README.md +95 -0
  2. package/bin/pabal-mcp.js +6 -0
  3. package/dist/src/core/clients/app-store-factory.d.ts +29 -0
  4. package/dist/src/core/clients/app-store-factory.js +72 -0
  5. package/dist/src/core/clients/client-factory-helpers.d.ts +7 -0
  6. package/dist/src/core/clients/client-factory-helpers.js +10 -0
  7. package/dist/src/core/clients/google-play-factory.d.ts +29 -0
  8. package/dist/src/core/clients/google-play-factory.js +72 -0
  9. package/dist/src/core/clients/types.d.ts +8 -0
  10. package/dist/src/core/clients/types.js +1 -0
  11. package/dist/src/core/helpers/formatters.d.ts +3 -0
  12. package/dist/src/core/helpers/formatters.js +38 -0
  13. package/dist/src/core/helpers/registration.d.ts +21 -0
  14. package/dist/src/core/helpers/registration.js +21 -0
  15. package/dist/src/core/helpers/translate-release-notes.d.ts +46 -0
  16. package/dist/src/core/helpers/translate-release-notes.js +87 -0
  17. package/dist/src/core/services/app-resolution-service.d.ts +14 -0
  18. package/dist/src/core/services/app-resolution-service.js +35 -0
  19. package/dist/src/core/services/app-store-service.d.ts +41 -0
  20. package/dist/src/core/services/app-store-service.js +266 -0
  21. package/dist/src/core/services/google-play-service.d.ts +36 -0
  22. package/dist/src/core/services/google-play-service.js +203 -0
  23. package/dist/src/core/services/service-helpers.d.ts +15 -0
  24. package/dist/src/core/services/service-helpers.js +31 -0
  25. package/dist/src/core/services/types.d.ts +81 -0
  26. package/dist/src/core/services/types.js +1 -0
  27. package/dist/src/core/workflows/version-info.d.ts +29 -0
  28. package/dist/src/core/workflows/version-info.js +100 -0
  29. package/dist/src/index.d.ts +10 -0
  30. package/dist/src/index.js +279 -0
  31. package/dist/src/packages/common/errors/app-error.d.ts +39 -0
  32. package/dist/src/packages/common/errors/app-error.js +134 -0
  33. package/dist/src/packages/common/errors/error-codes.d.ts +63 -0
  34. package/dist/src/packages/common/errors/error-codes.js +71 -0
  35. package/dist/src/packages/common/errors/status-codes.d.ts +10 -0
  36. package/dist/src/packages/common/errors/status-codes.js +9 -0
  37. package/dist/src/packages/configs/aso-config/constants.d.ts +14 -0
  38. package/dist/src/packages/configs/aso-config/constants.js +102 -0
  39. package/dist/src/packages/configs/aso-config/locale-guards.d.ts +3 -0
  40. package/dist/src/packages/configs/aso-config/locale-guards.js +7 -0
  41. package/dist/src/packages/configs/aso-config/store.d.ts +11 -0
  42. package/dist/src/packages/configs/aso-config/store.js +11 -0
  43. package/dist/src/packages/configs/aso-config/types.d.ts +98 -0
  44. package/dist/src/packages/configs/aso-config/types.js +2 -0
  45. package/dist/src/packages/configs/aso-config/utils.d.ts +43 -0
  46. package/dist/src/packages/configs/aso-config/utils.js +223 -0
  47. package/dist/src/packages/configs/secrets-config/config.d.ts +12 -0
  48. package/dist/src/packages/configs/secrets-config/config.js +187 -0
  49. package/dist/src/packages/configs/secrets-config/constants.d.ts +1 -0
  50. package/dist/src/packages/configs/secrets-config/constants.js +1 -0
  51. package/dist/src/packages/configs/secrets-config/errors.d.ts +9 -0
  52. package/dist/src/packages/configs/secrets-config/errors.js +15 -0
  53. package/dist/src/packages/configs/secrets-config/registered-apps.d.ts +52 -0
  54. package/dist/src/packages/configs/secrets-config/registered-apps.js +108 -0
  55. package/dist/src/packages/configs/secrets-config/schemas.d.ts +21 -0
  56. package/dist/src/packages/configs/secrets-config/schemas.js +9 -0
  57. package/dist/src/packages/configs/secrets-config/types.d.ts +8 -0
  58. package/dist/src/packages/configs/secrets-config/types.js +1 -0
  59. package/dist/src/packages/stores/app-store/api-converters.d.ts +26 -0
  60. package/dist/src/packages/stores/app-store/api-converters.js +131 -0
  61. package/dist/src/packages/stores/app-store/api-endpoints.d.ts +33 -0
  62. package/dist/src/packages/stores/app-store/api-endpoints.js +157 -0
  63. package/dist/src/packages/stores/app-store/auth.d.ts +12 -0
  64. package/dist/src/packages/stores/app-store/auth.js +36 -0
  65. package/dist/src/packages/stores/app-store/client.d.ts +78 -0
  66. package/dist/src/packages/stores/app-store/client.js +637 -0
  67. package/dist/src/packages/stores/app-store/constants.d.ts +11 -0
  68. package/dist/src/packages/stores/app-store/constants.js +38 -0
  69. package/dist/src/packages/stores/app-store/generated-types.d.ts +118537 -0
  70. package/dist/src/packages/stores/app-store/generated-types.js +5 -0
  71. package/dist/src/packages/stores/app-store/types.d.ts +39 -0
  72. package/dist/src/packages/stores/app-store/types.js +9 -0
  73. package/dist/src/packages/stores/app-store/verify-auth.d.ts +16 -0
  74. package/dist/src/packages/stores/app-store/verify-auth.js +34 -0
  75. package/dist/src/packages/stores/play-store/api-converters.d.ts +58 -0
  76. package/dist/src/packages/stores/play-store/api-converters.js +209 -0
  77. package/dist/src/packages/stores/play-store/api-endpoints.d.ts +68 -0
  78. package/dist/src/packages/stores/play-store/api-endpoints.js +145 -0
  79. package/dist/src/packages/stores/play-store/client.d.ts +55 -0
  80. package/dist/src/packages/stores/play-store/client.js +628 -0
  81. package/dist/src/packages/stores/play-store/constants.d.ts +10 -0
  82. package/dist/src/packages/stores/play-store/constants.js +17 -0
  83. package/dist/src/packages/stores/play-store/types.d.ts +146 -0
  84. package/dist/src/packages/stores/play-store/types.js +9 -0
  85. package/dist/src/packages/stores/play-store/verify-auth.d.ts +13 -0
  86. package/dist/src/packages/stores/play-store/verify-auth.js +31 -0
  87. package/dist/src/tools/apps/add.d.ts +28 -0
  88. package/dist/src/tools/apps/add.js +307 -0
  89. package/dist/src/tools/apps/init.d.ts +58 -0
  90. package/dist/src/tools/apps/init.js +390 -0
  91. package/dist/src/tools/apps/search.d.ts +33 -0
  92. package/dist/src/tools/apps/search.js +147 -0
  93. package/dist/src/tools/aso/pull.d.ts +22 -0
  94. package/dist/src/tools/aso/pull.js +264 -0
  95. package/dist/src/tools/aso/push.d.ts +23 -0
  96. package/dist/src/tools/aso/push.js +189 -0
  97. package/dist/src/tools/auth/app-store.d.ts +9 -0
  98. package/dist/src/tools/auth/app-store.js +34 -0
  99. package/dist/src/tools/auth/check.d.ts +14 -0
  100. package/dist/src/tools/auth/check.js +50 -0
  101. package/dist/src/tools/auth/play-store.d.ts +9 -0
  102. package/dist/src/tools/auth/play-store.js +30 -0
  103. package/dist/src/tools/release/check-versions.d.ts +14 -0
  104. package/dist/src/tools/release/check-versions.js +65 -0
  105. package/dist/src/tools/release/create.d.ts +23 -0
  106. package/dist/src/tools/release/create.js +128 -0
  107. package/dist/src/tools/release/pull-notes.d.ts +22 -0
  108. package/dist/src/tools/release/pull-notes.js +151 -0
  109. package/dist/src/tools/release/update-notes.d.ts +110 -0
  110. package/dist/src/tools/release/update-notes.js +537 -0
  111. package/package.json +71 -0
@@ -0,0 +1,266 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
+ import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
4
+ import { verifyAppStoreAuth } from "../../packages/stores/app-store/verify-auth.js";
5
+ import { createAppStoreClient } from "../../core/clients/app-store-factory.js";
6
+ import { checkPushPrerequisites, serviceFailure, toServiceResult, updateRegisteredLocales, } from "./service-helpers.js";
7
+ /**
8
+ * App Store-facing service layer that wraps client creation and common operations.
9
+ * Keeps MCP tools independent from client factories and SDK details.
10
+ */
11
+ export class AppStoreService {
12
+ getClientOrThrow(bundleId, existingClient) {
13
+ if (existingClient)
14
+ return existingClient;
15
+ const clientResult = this.createClient(bundleId);
16
+ if (!clientResult.success) {
17
+ throw clientResult.error;
18
+ }
19
+ return clientResult.data;
20
+ }
21
+ createClient(bundleId) {
22
+ return toServiceResult(createAppStoreClient({ bundleId }));
23
+ }
24
+ /**
25
+ * List released apps. Uses a fresh client to ensure working directory independence.
26
+ */
27
+ async listReleasedApps() {
28
+ try {
29
+ const client = this.getClientOrThrow("dummy");
30
+ const apps = await client.listAllApps({ onlyReleased: true });
31
+ return { success: true, data: apps };
32
+ }
33
+ catch (error) {
34
+ return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_LIST_APPS_FAILED));
35
+ }
36
+ }
37
+ /**
38
+ * Fetch a single app info (with locales) by bundleId.
39
+ */
40
+ async fetchAppInfo(bundleId, existingClient) {
41
+ try {
42
+ const client = this.getClientOrThrow(bundleId || "dummy", existingClient);
43
+ const apps = await client.listAllApps({ onlyReleased: true });
44
+ const app = apps.find((a) => a.bundleId === bundleId);
45
+ if (!app) {
46
+ return { found: false };
47
+ }
48
+ const supportedLocales = await client.getSupportedLocales(app.id);
49
+ return {
50
+ found: true,
51
+ appId: app.id,
52
+ name: app.name,
53
+ supportedLocales,
54
+ };
55
+ }
56
+ catch (error) {
57
+ return {
58
+ found: false,
59
+ error: AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_FETCH_APP_INFO_FAILED, "Failed to fetch App Store app info"),
60
+ };
61
+ }
62
+ }
63
+ async getLatestVersion(bundleId, existingClient) {
64
+ try {
65
+ const client = this.getClientOrThrow(bundleId, existingClient);
66
+ const latestVersion = await client.getLatestVersion();
67
+ if (!latestVersion) {
68
+ return { found: false };
69
+ }
70
+ const versionString = latestVersion.attributes?.versionString ?? "";
71
+ const appStoreState = latestVersion.attributes?.appStoreState;
72
+ return {
73
+ found: true,
74
+ versionString,
75
+ state: appStoreState ?? "UNKNOWN",
76
+ };
77
+ }
78
+ catch (error) {
79
+ return {
80
+ found: false,
81
+ error: AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_GET_LATEST_VERSION_FAILED, "Failed to fetch latest App Store version"),
82
+ };
83
+ }
84
+ }
85
+ async updateReleaseNotes(bundleId, releaseNotes, versionId, supportedLocales) {
86
+ try {
87
+ const client = this.getClientOrThrow(bundleId);
88
+ // Determine target versionId
89
+ let targetVersionId = versionId;
90
+ if (!targetVersionId) {
91
+ const versions = await client.getAllVersions();
92
+ const editableVersion = versions.find((v) => v.attributes?.appStoreState === "PREPARE_FOR_SUBMISSION");
93
+ if (!editableVersion) {
94
+ return serviceFailure(AppError.notFound(ERROR_CODES.APP_STORE_VERSION_NOT_EDITABLE, "No editable version found for release notes update"));
95
+ }
96
+ targetVersionId = editableVersion.id;
97
+ }
98
+ // Filter locales if supportedLocales provided
99
+ const localesToUpdate = supportedLocales
100
+ ? Object.keys(releaseNotes).filter((locale) => supportedLocales.includes(locale))
101
+ : Object.keys(releaseNotes);
102
+ const updated = [];
103
+ const failed = [];
104
+ for (const locale of localesToUpdate) {
105
+ try {
106
+ await client.updateWhatsNew({
107
+ versionId: targetVersionId,
108
+ locale,
109
+ whatsNew: releaseNotes[locale],
110
+ });
111
+ updated.push(locale);
112
+ }
113
+ catch (error) {
114
+ const msg = error instanceof Error ? error.message : String(error);
115
+ failed.push({ locale, error: msg });
116
+ }
117
+ }
118
+ const success = failed.length === 0;
119
+ const partialError = !success
120
+ ? AppError.wrap(failed[0]?.error ?? "Failed to update some release notes", HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_UPDATE_RELEASE_NOTES_PARTIAL)
121
+ : undefined;
122
+ if (!success) {
123
+ return serviceFailure(partialError ??
124
+ AppError.internal(ERROR_CODES.APP_STORE_UPDATE_RELEASE_NOTES_FAILED, "Failed to update App Store release notes"));
125
+ }
126
+ return {
127
+ success: true,
128
+ data: { updated, failed },
129
+ };
130
+ }
131
+ catch (error) {
132
+ return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_UPDATE_RELEASE_NOTES_FAILED, "Failed to update App Store release notes"));
133
+ }
134
+ }
135
+ async pullReleaseNotes(bundleId) {
136
+ try {
137
+ const client = this.getClientOrThrow(bundleId);
138
+ const releaseNotes = await client.pullReleaseNotes();
139
+ return { success: true, data: releaseNotes };
140
+ }
141
+ catch (error) {
142
+ return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_PULL_RELEASE_NOTES_FAILED, "Failed to pull App Store release notes"));
143
+ }
144
+ }
145
+ async createVersion(bundleId, versionString, autoIncrement) {
146
+ try {
147
+ const client = this.getClientOrThrow(bundleId);
148
+ const version = autoIncrement
149
+ ? await client.createNewVersionWithAutoIncrement(versionString)
150
+ : await client.createNewVersion(versionString);
151
+ return {
152
+ success: true,
153
+ data: {
154
+ id: version.id,
155
+ versionString: version.attributes?.versionString ?? "",
156
+ state: version.attributes?.appStoreState,
157
+ },
158
+ };
159
+ }
160
+ catch (error) {
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
+ }
163
+ }
164
+ async pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, }) {
165
+ const skip = checkPushPrerequisites({
166
+ storeLabel: "App Store",
167
+ configured: Boolean(config.appStore),
168
+ identifierLabel: "bundleId",
169
+ identifier: bundleId,
170
+ hasData: Boolean(localAsoData.appStore),
171
+ dataPath: appStoreDataPath,
172
+ });
173
+ if (skip)
174
+ return { success: false, error: skip };
175
+ const ensuredBundleId = bundleId;
176
+ const appStoreData = localAsoData.appStore;
177
+ const client = this.getClientOrThrow(ensuredBundleId);
178
+ console.error(`[MCP] 📤 Pushing to App Store...`);
179
+ console.error(`[MCP] Bundle ID: ${bundleId}`);
180
+ try {
181
+ const localesToPush = Object.keys(appStoreData.locales);
182
+ const failedFieldsList = [];
183
+ for (const [locale, localeData] of Object.entries(appStoreData.locales)) {
184
+ console.error(`[AppStore] 📤 Pushing ${locale}...`);
185
+ const localeResult = await client.pushAsoData(localeData);
186
+ if (localeResult.failedFields && localeResult.failedFields.length > 0) {
187
+ failedFieldsList.push({
188
+ locale,
189
+ fields: localeResult.failedFields,
190
+ });
191
+ console.error(`[AppStore] ⚠️ ${locale} partially updated (failed fields: ${localeResult.failedFields.join(", ")})`);
192
+ }
193
+ else {
194
+ console.error(`[AppStore] ✅ ${locale} uploaded successfully`);
195
+ }
196
+ }
197
+ try {
198
+ const updated = updateRegisteredLocales(ensuredBundleId, "appStore", localesToPush);
199
+ if (updated) {
200
+ console.error(`[MCP] ✅ Updated registered-apps.json with ${localesToPush.length} App Store locales`);
201
+ }
202
+ }
203
+ catch (updateError) {
204
+ console.error(`[MCP] ⚠️ Failed to update registered-apps.json: ${updateError instanceof Error
205
+ ? updateError.message
206
+ : String(updateError)}`);
207
+ }
208
+ if (failedFieldsList.length > 0) {
209
+ return {
210
+ success: true,
211
+ localesPushed: localesToPush,
212
+ failedFields: failedFieldsList,
213
+ };
214
+ }
215
+ return {
216
+ success: true,
217
+ localesPushed: localesToPush,
218
+ };
219
+ }
220
+ catch (error) {
221
+ const wrapped = AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_PUSH_FAILED, error instanceof Error ? error.message : String(error));
222
+ if (wrapped.message.includes("409 Conflict") &&
223
+ wrapped.message.includes("STATE_ERROR")) {
224
+ console.error(`[AppStore] 🔄 STATE_ERROR detected. New version needed.`);
225
+ try {
226
+ const version = await client.createNewVersionWithAutoIncrement();
227
+ const versionId = version.id;
228
+ const versionString = version.attributes?.versionString ?? "";
229
+ const locales = Object.keys(appStoreData.locales);
230
+ console.error(`[AppStore] ✅ New version ${versionString} created.`);
231
+ return {
232
+ success: false,
233
+ error: AppError.conflict(ERROR_CODES.APP_STORE_STATE_ERROR, "New version required"),
234
+ needsNewVersion: true,
235
+ versionInfo: {
236
+ versionId,
237
+ versionString,
238
+ locales,
239
+ },
240
+ };
241
+ }
242
+ catch (versionError) {
243
+ const versionMsg = versionError instanceof Error
244
+ ? versionError.message
245
+ : String(versionError);
246
+ return {
247
+ success: false,
248
+ error: AppError.wrap(versionError, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_CREATE_VERSION_FOR_STATE_ERROR_FAILED, `Failed to create new version: ${versionMsg}`),
249
+ };
250
+ }
251
+ }
252
+ console.error(`[AppStore] ❌ Push failed`, error);
253
+ return { success: false, error: wrapped };
254
+ }
255
+ }
256
+ async verifyAuth(expirationSeconds = 300) {
257
+ const result = await verifyAppStoreAuth({ expirationSeconds });
258
+ if (!result.success) {
259
+ return {
260
+ success: false,
261
+ error: AppError.wrap(result.error ?? "Unknown error", HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_VERIFY_AUTH_FAILED, "Failed to verify App Store auth"),
262
+ };
263
+ }
264
+ return { success: true, data: result.data };
265
+ }
266
+ }
@@ -0,0 +1,36 @@
1
+ import type { GooglePlayReleaseNote } from "../../packages/configs/aso-config/types.js";
2
+ import type { PreparedAsoData } from "../../packages/configs/aso-config/utils.js";
3
+ import type { EnvConfig } from "../../packages/configs/secrets-config/types.js";
4
+ import type { GooglePlayClient } from "../../packages/stores/play-store/client.js";
5
+ import { type MaybeResult, type ServiceResult, type GooglePlayReleaseInfo, type UpdatedReleaseNotesResult, type PushAsoResult, type CreatedGooglePlayVersion, type VerifyAuthResult } from "./types.js";
6
+ interface GooglePlayAppInfo {
7
+ name?: string;
8
+ supportedLocales?: string[];
9
+ }
10
+ /**
11
+ * Google Play-facing service layer that wraps client creation and common operations.
12
+ * Keeps MCP tools independent from client factories and SDK details.
13
+ */
14
+ export declare class GooglePlayService {
15
+ private getClientOrThrow;
16
+ createClient(packageName: string): ServiceResult<GooglePlayClient>;
17
+ /**
18
+ * Fetch a single app info (with locales) by packageName.
19
+ */
20
+ fetchAppInfo(packageName: string): Promise<MaybeResult<GooglePlayAppInfo>>;
21
+ getLatestProductionRelease(packageName: string): Promise<MaybeResult<GooglePlayReleaseInfo>>;
22
+ updateReleaseNotes(packageName: string, releaseNotes: Record<string, string>, track?: string, supportedLocales?: string[]): Promise<ServiceResult<UpdatedReleaseNotesResult>>;
23
+ pullReleaseNotes(packageName: string): Promise<ServiceResult<GooglePlayReleaseNote[]>>;
24
+ createVersion(packageName: string, versionString: string, versionCodes: number[]): Promise<ServiceResult<CreatedGooglePlayVersion>>;
25
+ pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, }: {
26
+ config: EnvConfig;
27
+ packageName?: string;
28
+ localAsoData: PreparedAsoData;
29
+ googlePlayDataPath: string;
30
+ }): Promise<PushAsoResult>;
31
+ verifyAuth(): Promise<VerifyAuthResult<{
32
+ client_email: string;
33
+ project_id: string;
34
+ }>>;
35
+ }
36
+ export {};
@@ -0,0 +1,203 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
+ import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
4
+ import { verifyPlayStoreAuth } from "../../packages/stores/play-store/verify-auth.js";
5
+ import { createGooglePlayClient } from "../../core/clients/google-play-factory.js";
6
+ import { checkPushPrerequisites, serviceFailure, toServiceResult, updateRegisteredLocales, } from "./service-helpers.js";
7
+ /**
8
+ * Google Play-facing service layer that wraps client creation and common operations.
9
+ * Keeps MCP tools independent from client factories and SDK details.
10
+ */
11
+ export class GooglePlayService {
12
+ getClientOrThrow(packageName, existingClient) {
13
+ if (existingClient)
14
+ return existingClient;
15
+ const clientResult = this.createClient(packageName);
16
+ if (!clientResult.success) {
17
+ throw clientResult.error;
18
+ }
19
+ return clientResult.data;
20
+ }
21
+ createClient(packageName) {
22
+ return toServiceResult(createGooglePlayClient({ packageName }));
23
+ }
24
+ /**
25
+ * Fetch a single app info (with locales) by packageName.
26
+ */
27
+ async fetchAppInfo(packageName) {
28
+ try {
29
+ const client = this.getClientOrThrow(packageName);
30
+ const appInfo = await client.verifyAppAccess();
31
+ return {
32
+ found: true,
33
+ name: appInfo.title,
34
+ supportedLocales: appInfo.supportedLocales,
35
+ };
36
+ }
37
+ catch (error) {
38
+ return {
39
+ found: false,
40
+ error: AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_FETCH_APP_INFO_FAILED, "Failed to fetch Google Play app info"),
41
+ };
42
+ }
43
+ }
44
+ async getLatestProductionRelease(packageName) {
45
+ try {
46
+ const client = this.getClientOrThrow(packageName);
47
+ const latestRelease = await client.getLatestProductionRelease();
48
+ if (!latestRelease) {
49
+ return { found: false };
50
+ }
51
+ const { versionName, releaseName, status, versionCodes } = latestRelease;
52
+ return {
53
+ found: true,
54
+ versionName,
55
+ releaseName,
56
+ status,
57
+ versionCodes,
58
+ };
59
+ }
60
+ catch (error) {
61
+ return {
62
+ found: false,
63
+ error: AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_GET_LATEST_RELEASE_FAILED, "Failed to fetch latest Google Play release"),
64
+ };
65
+ }
66
+ }
67
+ async updateReleaseNotes(packageName, releaseNotes, track, supportedLocales) {
68
+ try {
69
+ const client = this.getClientOrThrow(packageName);
70
+ const filteredReleaseNotes = {};
71
+ if (supportedLocales) {
72
+ for (const locale of supportedLocales) {
73
+ if (releaseNotes[locale]) {
74
+ filteredReleaseNotes[locale] = releaseNotes[locale];
75
+ }
76
+ }
77
+ }
78
+ else {
79
+ Object.assign(filteredReleaseNotes, releaseNotes);
80
+ }
81
+ if (Object.keys(filteredReleaseNotes).length === 0) {
82
+ return serviceFailure(AppError.validation(ERROR_CODES.GOOGLE_PLAY_RELEASE_NOTES_EMPTY, "No supported locales found in release notes"));
83
+ }
84
+ try {
85
+ const updateResult = await client.updateReleaseNotes({
86
+ releaseNotes: filteredReleaseNotes,
87
+ track: track ?? "production",
88
+ });
89
+ const success = updateResult.failed.length === 0;
90
+ const partialError = !success
91
+ ? AppError.wrap(updateResult.failed[0]?.error ??
92
+ "Failed to update some release notes", HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_UPDATE_RELEASE_NOTES_PARTIAL)
93
+ : undefined;
94
+ if (!success) {
95
+ return {
96
+ success: false,
97
+ error: partialError ??
98
+ AppError.internal(ERROR_CODES.GOOGLE_PLAY_UPDATE_RELEASE_NOTES_FAILED, "Failed to update Google Play release notes"),
99
+ };
100
+ }
101
+ return {
102
+ success: true,
103
+ data: {
104
+ updated: updateResult.updated,
105
+ failed: updateResult.failed,
106
+ },
107
+ };
108
+ }
109
+ catch (error) {
110
+ const msg = error instanceof Error ? error.message : String(error);
111
+ return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_UPDATE_RELEASE_NOTES_FAILED, msg));
112
+ }
113
+ }
114
+ catch (error) {
115
+ return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_UPDATE_RELEASE_NOTES_FAILED, "Failed to update Google Play release notes"));
116
+ }
117
+ }
118
+ async pullReleaseNotes(packageName) {
119
+ try {
120
+ const client = this.getClientOrThrow(packageName);
121
+ const releaseNotes = await client.pullProductionReleaseNotes();
122
+ return { success: true, data: releaseNotes };
123
+ }
124
+ catch (error) {
125
+ return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_PULL_RELEASE_NOTES_FAILED, "Failed to pull Google Play release notes"));
126
+ }
127
+ }
128
+ async createVersion(packageName, versionString, versionCodes) {
129
+ try {
130
+ const client = this.getClientOrThrow(packageName);
131
+ await client.createProductionRelease({
132
+ versionCodes,
133
+ releaseName: versionString,
134
+ status: "draft",
135
+ });
136
+ return {
137
+ success: true,
138
+ data: {
139
+ versionName: versionString,
140
+ versionCodes,
141
+ status: "DRAFT",
142
+ },
143
+ };
144
+ }
145
+ catch (error) {
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
+ }
148
+ }
149
+ async pushAsoData({ config, packageName, localAsoData, googlePlayDataPath, }) {
150
+ const skip = checkPushPrerequisites({
151
+ storeLabel: "Google Play",
152
+ configured: Boolean(config.playStore),
153
+ identifierLabel: "packageName",
154
+ identifier: packageName,
155
+ hasData: Boolean(localAsoData.googlePlay),
156
+ dataPath: googlePlayDataPath,
157
+ });
158
+ if (skip)
159
+ return { success: false, error: skip };
160
+ const ensuredPackage = packageName;
161
+ const googlePlayData = localAsoData.googlePlay;
162
+ const client = this.getClientOrThrow(ensuredPackage);
163
+ console.error(`[MCP] 📤 Pushing to Google Play...`);
164
+ console.error(`[MCP] Package: ${packageName}`);
165
+ try {
166
+ const localesToPush = Object.keys(googlePlayData.locales);
167
+ for (const locale of localesToPush) {
168
+ console.error(`[GooglePlay] 📤 Preparing locale: ${locale}`);
169
+ }
170
+ await client.pushMultilingualAsoData(googlePlayData);
171
+ try {
172
+ const updated = updateRegisteredLocales(ensuredPackage, "googlePlay", localesToPush);
173
+ if (updated) {
174
+ console.error(`[MCP] ✅ Updated registered-apps.json with ${localesToPush.length} Google Play locales`);
175
+ }
176
+ }
177
+ catch (updateError) {
178
+ console.error(`[MCP] ⚠️ Failed to update registered-apps.json: ${updateError instanceof Error
179
+ ? updateError.message
180
+ : String(updateError)}`);
181
+ }
182
+ return {
183
+ success: true,
184
+ localesPushed: localesToPush,
185
+ };
186
+ }
187
+ catch (error) {
188
+ const wrapped = AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_PUSH_FAILED, error instanceof Error ? error.message : String(error));
189
+ console.error(`[GooglePlay] ❌ Push failed: ${wrapped.message}`, error);
190
+ return { success: false, error: wrapped };
191
+ }
192
+ }
193
+ async verifyAuth() {
194
+ const result = verifyPlayStoreAuth();
195
+ if (!result.success) {
196
+ return {
197
+ success: false,
198
+ error: AppError.wrap(result.error ?? "Unknown error", HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_VERIFY_AUTH_FAILED, "Failed to verify Google Play auth"),
199
+ };
200
+ }
201
+ return { success: true, data: result.data };
202
+ }
203
+ }
@@ -0,0 +1,15 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import type { ClientFactoryResult } from "../clients/types.js";
3
+ import type { ServiceResult } from "./types.js";
4
+ export declare const toServiceResult: <T>(clientResult: ClientFactoryResult<T>) => ServiceResult<T>;
5
+ export declare const serviceSuccess: <T>(data: T) => ServiceResult<T>;
6
+ export declare const serviceFailure: <T = never>(error: AppError) => ServiceResult<T>;
7
+ export declare const updateRegisteredLocales: (identifier: string, store: "appStore" | "googlePlay", locales: string[]) => boolean;
8
+ export declare const checkPushPrerequisites: ({ storeLabel, configured, identifierLabel, identifier, hasData, dataPath, }: {
9
+ storeLabel: string;
10
+ configured: boolean;
11
+ identifierLabel: string;
12
+ identifier?: string;
13
+ hasData: boolean;
14
+ dataPath?: string;
15
+ }) => AppError | null;
@@ -0,0 +1,31 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
+ import { updateAppSupportedLocales } from "../../packages/configs/secrets-config/registered-apps.js";
4
+ export const toServiceResult = (clientResult) => clientResult.success
5
+ ? { success: true, data: clientResult.client }
6
+ : { success: false, error: clientResult.error };
7
+ export const serviceSuccess = (data) => ({
8
+ success: true,
9
+ data,
10
+ });
11
+ export const serviceFailure = (error) => ({
12
+ success: false,
13
+ error,
14
+ });
15
+ export const updateRegisteredLocales = (identifier, store, locales) => updateAppSupportedLocales({ identifier, store, locales });
16
+ export const checkPushPrerequisites = ({ storeLabel, configured, identifierLabel, identifier, hasData, dataPath, }) => {
17
+ if (!configured) {
18
+ return AppError.badRequest(ERROR_CODES.CONFIG_NOT_FOUND_SKIP, `⏭️ Skipping ${storeLabel} (not configured in ~/.config/pabal-mcp/config.json)`);
19
+ }
20
+ if (!identifier) {
21
+ return AppError.validation(ERROR_CODES.IDENTIFIER_MISSING, `⏭️ Skipping ${storeLabel} (no ${identifierLabel} provided)`);
22
+ }
23
+ if (!hasData) {
24
+ console.error(`[MCP] ⏭️ Skipping ${storeLabel}: No data found after preparation`);
25
+ if (dataPath) {
26
+ console.error(`[MCP] Check if data exists in: ${dataPath}`);
27
+ }
28
+ return AppError.validation(ERROR_CODES.NO_DATA_FOUND, `⏭️ Skipping ${storeLabel} (no data found)`);
29
+ }
30
+ return null;
31
+ };
@@ -0,0 +1,81 @@
1
+ import type { AppError } from "../../packages/common/errors/app-error.js";
2
+ import type { RegisteredApp } from "../../packages/configs/secrets-config/registered-apps.js";
3
+ export type ServiceResult<T> = {
4
+ success: true;
5
+ data: T;
6
+ } | {
7
+ success: false;
8
+ error: AppError;
9
+ };
10
+ export type MaybeResult<T> = ({
11
+ found: true;
12
+ } & T) | {
13
+ found: false;
14
+ error?: AppError;
15
+ };
16
+ export interface StoreAppSummary {
17
+ id: string;
18
+ name: string;
19
+ bundleId: string;
20
+ sku: string;
21
+ isReleased: boolean;
22
+ }
23
+ export interface AppStoreVersionInfo {
24
+ versionString: string;
25
+ state: string;
26
+ }
27
+ export interface GooglePlayReleaseInfo {
28
+ versionName?: string;
29
+ releaseName?: string;
30
+ status?: string;
31
+ versionCodes: number[];
32
+ }
33
+ export interface CreatedAppStoreVersion {
34
+ id: string;
35
+ versionString: string;
36
+ state?: string;
37
+ }
38
+ export interface CreatedGooglePlayVersion {
39
+ versionName: string;
40
+ versionCodes: number[];
41
+ status: string;
42
+ releaseName?: string;
43
+ }
44
+ export interface UpdatedReleaseNotesResult {
45
+ updated: string[];
46
+ failed: Array<{
47
+ locale: string;
48
+ error: string;
49
+ }>;
50
+ }
51
+ export interface PushFailedFields {
52
+ locale: string;
53
+ fields: string[];
54
+ }
55
+ export type PushAsoResult = {
56
+ success: true;
57
+ localesPushed: string[];
58
+ failedFields?: PushFailedFields[];
59
+ } | {
60
+ success: false;
61
+ error: AppError;
62
+ needsNewVersion?: boolean;
63
+ versionInfo?: {
64
+ versionId: string;
65
+ versionString: string;
66
+ locales: string[];
67
+ };
68
+ };
69
+ export interface VerifyAuthResult<TPayload = Record<string, unknown>> {
70
+ success: boolean;
71
+ error?: AppError;
72
+ data?: TPayload;
73
+ }
74
+ export interface ResolvedAppContext {
75
+ app: RegisteredApp;
76
+ slug: string;
77
+ bundleId?: string;
78
+ packageName?: string;
79
+ hasAppStore: boolean;
80
+ hasGooglePlay: boolean;
81
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import { type StoreType } from "../../packages/configs/aso-config/types.js";
2
+ export interface LatestVersionsInput {
3
+ store?: StoreType;
4
+ bundleId?: string;
5
+ packageName?: string;
6
+ hasAppStore: boolean;
7
+ hasGooglePlay: boolean;
8
+ includePrompt?: boolean;
9
+ }
10
+ export interface LatestVersionsResult {
11
+ messages: string[];
12
+ appStore?: {
13
+ found: boolean;
14
+ versionString?: string;
15
+ state?: string;
16
+ error?: string;
17
+ skipped?: string;
18
+ };
19
+ googlePlay?: {
20
+ found: boolean;
21
+ versionName?: string;
22
+ versionCodes?: number[];
23
+ status?: string;
24
+ releaseName?: string;
25
+ error?: string;
26
+ skipped?: string;
27
+ };
28
+ }
29
+ export declare function getLatestVersions(input: LatestVersionsInput): Promise<LatestVersionsResult>;