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.
- package/README.md +95 -0
- package/bin/pabal-mcp.js +6 -0
- package/dist/src/core/clients/app-store-factory.d.ts +29 -0
- package/dist/src/core/clients/app-store-factory.js +72 -0
- package/dist/src/core/clients/client-factory-helpers.d.ts +7 -0
- package/dist/src/core/clients/client-factory-helpers.js +10 -0
- package/dist/src/core/clients/google-play-factory.d.ts +29 -0
- package/dist/src/core/clients/google-play-factory.js +72 -0
- package/dist/src/core/clients/types.d.ts +8 -0
- package/dist/src/core/clients/types.js +1 -0
- package/dist/src/core/helpers/formatters.d.ts +3 -0
- package/dist/src/core/helpers/formatters.js +38 -0
- package/dist/src/core/helpers/registration.d.ts +21 -0
- package/dist/src/core/helpers/registration.js +21 -0
- package/dist/src/core/helpers/translate-release-notes.d.ts +46 -0
- package/dist/src/core/helpers/translate-release-notes.js +87 -0
- package/dist/src/core/services/app-resolution-service.d.ts +14 -0
- package/dist/src/core/services/app-resolution-service.js +35 -0
- package/dist/src/core/services/app-store-service.d.ts +41 -0
- package/dist/src/core/services/app-store-service.js +266 -0
- package/dist/src/core/services/google-play-service.d.ts +36 -0
- package/dist/src/core/services/google-play-service.js +203 -0
- package/dist/src/core/services/service-helpers.d.ts +15 -0
- package/dist/src/core/services/service-helpers.js +31 -0
- package/dist/src/core/services/types.d.ts +81 -0
- package/dist/src/core/services/types.js +1 -0
- package/dist/src/core/workflows/version-info.d.ts +29 -0
- package/dist/src/core/workflows/version-info.js +100 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +279 -0
- package/dist/src/packages/common/errors/app-error.d.ts +39 -0
- package/dist/src/packages/common/errors/app-error.js +134 -0
- package/dist/src/packages/common/errors/error-codes.d.ts +63 -0
- package/dist/src/packages/common/errors/error-codes.js +71 -0
- package/dist/src/packages/common/errors/status-codes.d.ts +10 -0
- package/dist/src/packages/common/errors/status-codes.js +9 -0
- package/dist/src/packages/configs/aso-config/constants.d.ts +14 -0
- package/dist/src/packages/configs/aso-config/constants.js +102 -0
- package/dist/src/packages/configs/aso-config/locale-guards.d.ts +3 -0
- package/dist/src/packages/configs/aso-config/locale-guards.js +7 -0
- package/dist/src/packages/configs/aso-config/store.d.ts +11 -0
- package/dist/src/packages/configs/aso-config/store.js +11 -0
- package/dist/src/packages/configs/aso-config/types.d.ts +98 -0
- package/dist/src/packages/configs/aso-config/types.js +2 -0
- package/dist/src/packages/configs/aso-config/utils.d.ts +43 -0
- package/dist/src/packages/configs/aso-config/utils.js +223 -0
- package/dist/src/packages/configs/secrets-config/config.d.ts +12 -0
- package/dist/src/packages/configs/secrets-config/config.js +187 -0
- package/dist/src/packages/configs/secrets-config/constants.d.ts +1 -0
- package/dist/src/packages/configs/secrets-config/constants.js +1 -0
- package/dist/src/packages/configs/secrets-config/errors.d.ts +9 -0
- package/dist/src/packages/configs/secrets-config/errors.js +15 -0
- package/dist/src/packages/configs/secrets-config/registered-apps.d.ts +52 -0
- package/dist/src/packages/configs/secrets-config/registered-apps.js +108 -0
- package/dist/src/packages/configs/secrets-config/schemas.d.ts +21 -0
- package/dist/src/packages/configs/secrets-config/schemas.js +9 -0
- package/dist/src/packages/configs/secrets-config/types.d.ts +8 -0
- package/dist/src/packages/configs/secrets-config/types.js +1 -0
- package/dist/src/packages/stores/app-store/api-converters.d.ts +26 -0
- package/dist/src/packages/stores/app-store/api-converters.js +131 -0
- package/dist/src/packages/stores/app-store/api-endpoints.d.ts +33 -0
- package/dist/src/packages/stores/app-store/api-endpoints.js +157 -0
- package/dist/src/packages/stores/app-store/auth.d.ts +12 -0
- package/dist/src/packages/stores/app-store/auth.js +36 -0
- package/dist/src/packages/stores/app-store/client.d.ts +78 -0
- package/dist/src/packages/stores/app-store/client.js +637 -0
- package/dist/src/packages/stores/app-store/constants.d.ts +11 -0
- package/dist/src/packages/stores/app-store/constants.js +38 -0
- package/dist/src/packages/stores/app-store/generated-types.d.ts +118537 -0
- package/dist/src/packages/stores/app-store/generated-types.js +5 -0
- package/dist/src/packages/stores/app-store/types.d.ts +39 -0
- package/dist/src/packages/stores/app-store/types.js +9 -0
- package/dist/src/packages/stores/app-store/verify-auth.d.ts +16 -0
- package/dist/src/packages/stores/app-store/verify-auth.js +34 -0
- package/dist/src/packages/stores/play-store/api-converters.d.ts +58 -0
- package/dist/src/packages/stores/play-store/api-converters.js +209 -0
- package/dist/src/packages/stores/play-store/api-endpoints.d.ts +68 -0
- package/dist/src/packages/stores/play-store/api-endpoints.js +145 -0
- package/dist/src/packages/stores/play-store/client.d.ts +55 -0
- package/dist/src/packages/stores/play-store/client.js +628 -0
- package/dist/src/packages/stores/play-store/constants.d.ts +10 -0
- package/dist/src/packages/stores/play-store/constants.js +17 -0
- package/dist/src/packages/stores/play-store/types.d.ts +146 -0
- package/dist/src/packages/stores/play-store/types.js +9 -0
- package/dist/src/packages/stores/play-store/verify-auth.d.ts +13 -0
- package/dist/src/packages/stores/play-store/verify-auth.js +31 -0
- package/dist/src/tools/apps/add.d.ts +28 -0
- package/dist/src/tools/apps/add.js +307 -0
- package/dist/src/tools/apps/init.d.ts +58 -0
- package/dist/src/tools/apps/init.js +390 -0
- package/dist/src/tools/apps/search.d.ts +33 -0
- package/dist/src/tools/apps/search.js +147 -0
- package/dist/src/tools/aso/pull.d.ts +22 -0
- package/dist/src/tools/aso/pull.js +264 -0
- package/dist/src/tools/aso/push.d.ts +23 -0
- package/dist/src/tools/aso/push.js +189 -0
- package/dist/src/tools/auth/app-store.d.ts +9 -0
- package/dist/src/tools/auth/app-store.js +34 -0
- package/dist/src/tools/auth/check.d.ts +14 -0
- package/dist/src/tools/auth/check.js +50 -0
- package/dist/src/tools/auth/play-store.d.ts +9 -0
- package/dist/src/tools/auth/play-store.js +30 -0
- package/dist/src/tools/release/check-versions.d.ts +14 -0
- package/dist/src/tools/release/check-versions.js +65 -0
- package/dist/src/tools/release/create.d.ts +23 -0
- package/dist/src/tools/release/create.js +128 -0
- package/dist/src/tools/release/pull-notes.d.ts +22 -0
- package/dist/src/tools/release/pull-notes.js +151 -0
- package/dist/src/tools/release/update-notes.d.ts +110 -0
- package/dist/src/tools/release/update-notes.js +537 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://cursor.com/en/install-mcp?name=pabal-store-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInBhYmFsLXN0b3JlLWFwaS1tY3AiXX0%3D)
|
|
4
|
+
|
|
5
|
+
[](https://pabal.quartz.best/docs/en-US/pabal-web-mcp/README) [](https://pabal.quartz.best/docs/ko-KR/pabal-web-mcp/README)
|
|
6
|
+
|
|
7
|
+
# MCP server for App Store Connect & Play Console API
|
|
8
|
+
|
|
9
|
+
Up-to-date ASO workflows exposed as MCP tools. Run it as a stdio MCP server (Claude Code, Cursor, MCP Inspector, etc.) to manage metadata, releases, and store syncs without leaving your AI client.
|
|
10
|
+
|
|
11
|
+
> [!NOTE]
|
|
12
|
+
> Runs 100% locally on your machine, so credentials and cached ASO data never leave your environment (store API calls are made directly from your device).
|
|
13
|
+
|
|
14
|
+
<br>
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g pabal-store-api-mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Configure
|
|
25
|
+
|
|
26
|
+
1. Create config directory:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
mkdir -p ~/.config/pabal-store-api-mcp
|
|
30
|
+
chmod 700 ~/.config/pabal-store-api-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Add your credentials to `~/.config/pabal-store-api-mcp/config.json`:
|
|
34
|
+
- **App Store Connect**: API key (`.p8` file) with `issuerId` and `keyId`
|
|
35
|
+
- **Google Play**: Service account JSON key
|
|
36
|
+
|
|
37
|
+
3. Set up MCP client (Cursor, VS Code, Claude Code, etc.) to use `pabal-store-api-mcp`
|
|
38
|
+
|
|
39
|
+
<br>
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- ✅ **ASO Data Sync**: Pull/push metadata from App Store and Google Play
|
|
44
|
+
- ✅ **Release Management**: Create versions and update release notes
|
|
45
|
+
- ✅ **App Management**: Auto-register apps from store APIs
|
|
46
|
+
- ✅ **100% Local**: All operations run on your machine
|
|
47
|
+
|
|
48
|
+
<br>
|
|
49
|
+
|
|
50
|
+
## MCP Tools
|
|
51
|
+
|
|
52
|
+
- **Authentication**: `auth-check`
|
|
53
|
+
- **App Management**: `apps-init`, `apps-add`, `apps-search`
|
|
54
|
+
- **ASO Sync**: `aso-pull`, `aso-push`
|
|
55
|
+
- **Release Management**: `release-check-versions`, `release-create`, `release-pull-notes`, `release-update-notes`
|
|
56
|
+
|
|
57
|
+
<br>
|
|
58
|
+
|
|
59
|
+
## Documentation
|
|
60
|
+
|
|
61
|
+
📖 **[Pabal Web MCP Documentation (English)](https://pabal.quartz.best/docs/en-US/pabal-web-mcp/README)**
|
|
62
|
+
📖 **[Pabal Web MCP 문서 (한국어)](https://pabal.quartz.best/docs/ko-KR/pabal-web-mcp/README)**
|
|
63
|
+
|
|
64
|
+
<br>
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/quartz-labs-dev/pabal-store-api-mcp.git
|
|
70
|
+
cd pabal-store-api-mcp
|
|
71
|
+
yarn install
|
|
72
|
+
yarn dev:mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
<br>
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
|
80
|
+
|
|
81
|
+
<br>
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
<br>
|
|
86
|
+
|
|
87
|
+
## 🌐 Pabal Web
|
|
88
|
+
|
|
89
|
+
Want to manage ASO and SEO together? Check out **Pabal Web**.
|
|
90
|
+
|
|
91
|
+
[](https://pabal.quartz.best/)
|
|
92
|
+
|
|
93
|
+
**Pabal Web** is a Next.js-based web interface that provides a complete solution for unified management of ASO, SEO, Google Search Console indexing, and more.
|
|
94
|
+
|
|
95
|
+
👉 [Visit Pabal Web](https://pabal.quartz.best/)
|
package/bin/pabal-mcp.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AppStoreClient } from "../../packages/stores/app-store/client.js";
|
|
2
|
+
import type { ClientFactoryResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Result type for App Store client creation
|
|
5
|
+
*/
|
|
6
|
+
export type AppStoreClientResult = ClientFactoryResult<AppStoreClient>;
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for App Store client creation
|
|
9
|
+
*/
|
|
10
|
+
export interface AppStoreClientConfig {
|
|
11
|
+
bundleId: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create App Store client with fresh config loading and Result pattern
|
|
15
|
+
*
|
|
16
|
+
* Stateless factory - loads config on every call to ensure MCP works from any directory
|
|
17
|
+
*
|
|
18
|
+
* @param config - Bundle ID configuration
|
|
19
|
+
* @returns Result with client or error message
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const result = createAppStoreClient({ bundleId: "com.example.app" });
|
|
23
|
+
* if (result.success) {
|
|
24
|
+
* const data = await result.client.getApp();
|
|
25
|
+
* } else {
|
|
26
|
+
* console.error(result.error);
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
export declare function createAppStoreClient(config: AppStoreClientConfig): AppStoreClientResult;
|
|
@@ -0,0 +1,72 @@
|
|
|
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 { loadConfig } from "../../packages/configs/secrets-config/config.js";
|
|
5
|
+
import { AppStoreClient } from "../../packages/stores/app-store/client.js";
|
|
6
|
+
import { failure, isNonEmptyString, success } from "./client-factory-helpers.js";
|
|
7
|
+
/**
|
|
8
|
+
* Create App Store client with fresh config loading and Result pattern
|
|
9
|
+
*
|
|
10
|
+
* Stateless factory - loads config on every call to ensure MCP works from any directory
|
|
11
|
+
*
|
|
12
|
+
* @param config - Bundle ID configuration
|
|
13
|
+
* @returns Result with client or error message
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const result = createAppStoreClient({ bundleId: "com.example.app" });
|
|
17
|
+
* if (result.success) {
|
|
18
|
+
* const data = await result.client.getApp();
|
|
19
|
+
* } else {
|
|
20
|
+
* console.error(result.error);
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
export function createAppStoreClient(config) {
|
|
24
|
+
// Validate input
|
|
25
|
+
if (!config) {
|
|
26
|
+
return failure(AppError.badRequest(ERROR_CODES.APP_STORE_CLIENT_CONFIG_MISSING, "App Store client configuration is missing"));
|
|
27
|
+
}
|
|
28
|
+
const { bundleId } = config;
|
|
29
|
+
if (!isNonEmptyString(bundleId)) {
|
|
30
|
+
return failure(AppError.validation(ERROR_CODES.APP_STORE_BUNDLE_ID_INVALID, "Bundle ID is required and must be a non-empty string"));
|
|
31
|
+
}
|
|
32
|
+
// Load fresh config (stateless - works from any directory)
|
|
33
|
+
let appStoreConfig;
|
|
34
|
+
try {
|
|
35
|
+
const fullConfig = loadConfig();
|
|
36
|
+
appStoreConfig = fullConfig.appStore;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_CONFIG_LOAD_FAILED, "Failed to load App Store configuration"));
|
|
40
|
+
}
|
|
41
|
+
// Validate required credentials
|
|
42
|
+
if (!appStoreConfig) {
|
|
43
|
+
return failure(AppError.configMissing(ERROR_CODES.APP_STORE_CONFIG_MISSING, "App Store configuration is missing in config file"));
|
|
44
|
+
}
|
|
45
|
+
const { issuerId, keyId, privateKey } = appStoreConfig;
|
|
46
|
+
if (!isNonEmptyString(issuerId)) {
|
|
47
|
+
return failure(AppError.validation(ERROR_CODES.APP_STORE_ISSUER_ID_INVALID, "App Store Issuer ID is required in configuration"));
|
|
48
|
+
}
|
|
49
|
+
if (!isNonEmptyString(keyId)) {
|
|
50
|
+
return failure(AppError.validation(ERROR_CODES.APP_STORE_KEY_ID_INVALID, "App Store Key ID is required in configuration"));
|
|
51
|
+
}
|
|
52
|
+
if (!isNonEmptyString(privateKey)) {
|
|
53
|
+
return failure(AppError.validation(ERROR_CODES.APP_STORE_PRIVATE_KEY_INVALID, "App Store Private Key is required in configuration"));
|
|
54
|
+
}
|
|
55
|
+
// Validate private key format
|
|
56
|
+
if (!privateKey.includes("BEGIN PRIVATE KEY")) {
|
|
57
|
+
return failure(AppError.validation(ERROR_CODES.APP_STORE_PRIVATE_KEY_FORMAT_INVALID, "Invalid Private Key format. PEM format private key is required (must contain 'BEGIN PRIVATE KEY')"));
|
|
58
|
+
}
|
|
59
|
+
// Create client
|
|
60
|
+
try {
|
|
61
|
+
const client = new AppStoreClient({
|
|
62
|
+
bundleId,
|
|
63
|
+
issuerId,
|
|
64
|
+
keyId,
|
|
65
|
+
privateKey,
|
|
66
|
+
});
|
|
67
|
+
return success(client);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_CLIENT_CREATE_FAILED, "Failed to create App Store client"));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AppError } from "../../packages/common/errors/app-error.js";
|
|
2
|
+
import type { ClientFactoryResult } from "./types.js";
|
|
3
|
+
export type { ClientFactoryResult } from "./types.js";
|
|
4
|
+
export declare const success: <TClient>(client: TClient) => ClientFactoryResult<TClient>;
|
|
5
|
+
export declare const failure: <TClient>(error: AppError) => ClientFactoryResult<TClient>;
|
|
6
|
+
export declare const toErrorMessage: (error: unknown) => string;
|
|
7
|
+
export declare const isNonEmptyString: (value: unknown) => value is string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const success = (client) => ({
|
|
2
|
+
success: true,
|
|
3
|
+
client,
|
|
4
|
+
});
|
|
5
|
+
export const failure = (error) => ({
|
|
6
|
+
success: false,
|
|
7
|
+
error,
|
|
8
|
+
});
|
|
9
|
+
export const toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
10
|
+
export const isNonEmptyString = (value) => typeof value === "string" && Boolean(value.trim());
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { GooglePlayClient } from "../../packages/stores/play-store/client.js";
|
|
2
|
+
import type { ClientFactoryResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Result type for Google Play client creation
|
|
5
|
+
*/
|
|
6
|
+
export type GooglePlayClientResult = ClientFactoryResult<GooglePlayClient>;
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for Google Play client creation
|
|
9
|
+
*/
|
|
10
|
+
export interface GooglePlayClientConfig {
|
|
11
|
+
packageName: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create Google Play client with fresh config loading and Result pattern
|
|
15
|
+
*
|
|
16
|
+
* Stateless factory - loads config on every call to ensure MCP works from any directory
|
|
17
|
+
*
|
|
18
|
+
* @param config - Package name configuration
|
|
19
|
+
* @returns Result with client or error message
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const result = createGooglePlayClient({ packageName: "com.example.app" });
|
|
23
|
+
* if (result.success) {
|
|
24
|
+
* const data = await result.client.getApp();
|
|
25
|
+
* } else {
|
|
26
|
+
* console.error(result.error);
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
export declare function createGooglePlayClient(config: GooglePlayClientConfig): GooglePlayClientResult;
|
|
@@ -0,0 +1,72 @@
|
|
|
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 { loadConfig } from "../../packages/configs/secrets-config/config.js";
|
|
5
|
+
import { GooglePlayClient } from "../../packages/stores/play-store/client.js";
|
|
6
|
+
import { failure, isNonEmptyString, success } from "./client-factory-helpers.js";
|
|
7
|
+
/**
|
|
8
|
+
* Create Google Play client with fresh config loading and Result pattern
|
|
9
|
+
*
|
|
10
|
+
* Stateless factory - loads config on every call to ensure MCP works from any directory
|
|
11
|
+
*
|
|
12
|
+
* @param config - Package name configuration
|
|
13
|
+
* @returns Result with client or error message
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const result = createGooglePlayClient({ packageName: "com.example.app" });
|
|
17
|
+
* if (result.success) {
|
|
18
|
+
* const data = await result.client.getApp();
|
|
19
|
+
* } else {
|
|
20
|
+
* console.error(result.error);
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
export function createGooglePlayClient(config) {
|
|
24
|
+
// Validate input
|
|
25
|
+
if (!config) {
|
|
26
|
+
return failure(AppError.badRequest(ERROR_CODES.GOOGLE_PLAY_CLIENT_CONFIG_MISSING, "Google Play client configuration is missing"));
|
|
27
|
+
}
|
|
28
|
+
const { packageName } = config;
|
|
29
|
+
if (!isNonEmptyString(packageName)) {
|
|
30
|
+
return failure(AppError.validation(ERROR_CODES.GOOGLE_PLAY_PACKAGE_NAME_INVALID, "Package name is required and must be a non-empty string"));
|
|
31
|
+
}
|
|
32
|
+
// Load fresh config (stateless - works from any directory)
|
|
33
|
+
let playStoreConfig;
|
|
34
|
+
try {
|
|
35
|
+
const fullConfig = loadConfig();
|
|
36
|
+
playStoreConfig = fullConfig.playStore;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_CONFIG_LOAD_FAILED, "Failed to load Google Play configuration"));
|
|
40
|
+
}
|
|
41
|
+
// Validate required credentials
|
|
42
|
+
if (!playStoreConfig) {
|
|
43
|
+
return failure(AppError.configMissing(ERROR_CODES.GOOGLE_PLAY_CONFIG_MISSING, "Google Play configuration is missing in config file"));
|
|
44
|
+
}
|
|
45
|
+
const { serviceAccountJson } = playStoreConfig;
|
|
46
|
+
if (!isNonEmptyString(serviceAccountJson)) {
|
|
47
|
+
return failure(AppError.validation(ERROR_CODES.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_INVALID, "Google Play service account JSON is required in configuration"));
|
|
48
|
+
}
|
|
49
|
+
// Parse and validate service account JSON
|
|
50
|
+
let serviceAccountKey;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(serviceAccountJson);
|
|
53
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
54
|
+
return failure(AppError.validation(ERROR_CODES.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_FORMAT_INVALID, "Service account JSON must be a valid JSON object"));
|
|
55
|
+
}
|
|
56
|
+
serviceAccountKey = parsed;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return failure(AppError.wrap(error, HTTP_STATUS.UNPROCESSABLE_ENTITY, ERROR_CODES.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PARSE_FAILED, "Invalid Google Play service account JSON"));
|
|
60
|
+
}
|
|
61
|
+
// Create client
|
|
62
|
+
try {
|
|
63
|
+
const client = new GooglePlayClient({
|
|
64
|
+
packageName,
|
|
65
|
+
serviceAccountKey,
|
|
66
|
+
});
|
|
67
|
+
return success(client);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_CLIENT_CREATE_FAILED, "Failed to create Google Play client"));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PushAsoResult, UpdatedReleaseNotesResult } from "../../core/services/types.js";
|
|
2
|
+
export declare function formatPushResult(storeLabel: "App Store" | "Google Play", result: PushAsoResult): string;
|
|
3
|
+
export declare const formatReleaseNotesUpdate: (storeLabel: "App Store" | "Google Play", result: UpdatedReleaseNotesResult) => string[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const fieldDisplayNames = {
|
|
2
|
+
name: "Name",
|
|
3
|
+
subtitle: "Subtitle",
|
|
4
|
+
};
|
|
5
|
+
function formatFailedFields(failedFields) {
|
|
6
|
+
return failedFields
|
|
7
|
+
.map((f) => {
|
|
8
|
+
const fieldNames = f.fields.map((field) => fieldDisplayNames[field] || field);
|
|
9
|
+
return ` • ${f.locale}: ${fieldNames.join(", ")}`;
|
|
10
|
+
})
|
|
11
|
+
.join("\n");
|
|
12
|
+
}
|
|
13
|
+
export function formatPushResult(storeLabel, result) {
|
|
14
|
+
if (!result.success) {
|
|
15
|
+
if (result.needsNewVersion && result.versionInfo) {
|
|
16
|
+
const { versionString, versionId } = result.versionInfo;
|
|
17
|
+
return `✅ New version ${versionString} created (Version ID: ${versionId})`;
|
|
18
|
+
}
|
|
19
|
+
return `❌ ${storeLabel} push failed: ${result.error.message}`;
|
|
20
|
+
}
|
|
21
|
+
if (result.failedFields && result.failedFields.length > 0) {
|
|
22
|
+
return `⚠️ ${storeLabel} data pushed with partial failures (${result.localesPushed.length} locales)\n${formatFailedFields(result.failedFields)}`;
|
|
23
|
+
}
|
|
24
|
+
return `✅ ${storeLabel} data pushed (${result.localesPushed.length} locales)`;
|
|
25
|
+
}
|
|
26
|
+
export const formatReleaseNotesUpdate = (storeLabel, result) => {
|
|
27
|
+
const lines = [];
|
|
28
|
+
lines.push(`**${storeLabel}**`);
|
|
29
|
+
if (result.updated.length > 0) {
|
|
30
|
+
lines.push(` ✅ Updated: ${result.updated.join(", ")}`);
|
|
31
|
+
}
|
|
32
|
+
if (result.failed.length > 0) {
|
|
33
|
+
for (const fail of result.failed) {
|
|
34
|
+
lines.push(` ❌ ${fail.locale}: ${fail.error}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return lines;
|
|
38
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RegisteredAppStoreInfo, RegisteredGooglePlayInfo } from "../../packages/configs/secrets-config/registered-apps.js";
|
|
2
|
+
type AppStoreAppInfo = {
|
|
3
|
+
found: boolean;
|
|
4
|
+
appId?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
supportedLocales?: string[];
|
|
7
|
+
};
|
|
8
|
+
type GooglePlayAppInfo = {
|
|
9
|
+
found: boolean;
|
|
10
|
+
name?: string;
|
|
11
|
+
supportedLocales?: string[];
|
|
12
|
+
};
|
|
13
|
+
export declare function toRegisteredAppStoreInfo({ bundleId, appInfo, }: {
|
|
14
|
+
bundleId: string;
|
|
15
|
+
appInfo: AppStoreAppInfo;
|
|
16
|
+
}): RegisteredAppStoreInfo | undefined;
|
|
17
|
+
export declare function toRegisteredGooglePlayInfo({ packageName, appInfo, }: {
|
|
18
|
+
packageName: string;
|
|
19
|
+
appInfo: GooglePlayAppInfo;
|
|
20
|
+
}): RegisteredGooglePlayInfo | undefined;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function toRegisteredAppStoreInfo({ bundleId, appInfo, }) {
|
|
2
|
+
if (!appInfo.found) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
return {
|
|
6
|
+
bundleId,
|
|
7
|
+
appId: appInfo.appId,
|
|
8
|
+
name: appInfo.name,
|
|
9
|
+
supportedLocales: appInfo.supportedLocales,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function toRegisteredGooglePlayInfo({ packageName, appInfo, }) {
|
|
13
|
+
if (!appInfo.found) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
packageName,
|
|
18
|
+
name: appInfo.name,
|
|
19
|
+
supportedLocales: appInfo.supportedLocales,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release notes translation utilities.
|
|
3
|
+
* Prepares translation requests per store and gathers supported locales from registered apps.
|
|
4
|
+
*/
|
|
5
|
+
import type { RegisteredApp } from "../../packages/configs/secrets-config/registered-apps.js";
|
|
6
|
+
import type { StoreType } from "../../packages/configs/aso-config/types.js";
|
|
7
|
+
export interface TranslationRequest {
|
|
8
|
+
sourceText: string;
|
|
9
|
+
sourceLocale: string;
|
|
10
|
+
targetLocales: string[];
|
|
11
|
+
store: StoreType;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Collect supported locales from registered app info
|
|
15
|
+
*/
|
|
16
|
+
export declare function collectSupportedLocales({ app, store, }: {
|
|
17
|
+
app: RegisteredApp;
|
|
18
|
+
store: StoreType;
|
|
19
|
+
}): {
|
|
20
|
+
appStore: string[];
|
|
21
|
+
googlePlay: string[];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Create translation requests per store
|
|
25
|
+
*/
|
|
26
|
+
export declare function createTranslationRequests({ store, targetLocales, sourceLocale, sourceText, }: {
|
|
27
|
+
store: StoreType;
|
|
28
|
+
targetLocales: {
|
|
29
|
+
appStore: string[];
|
|
30
|
+
googlePlay: string[];
|
|
31
|
+
};
|
|
32
|
+
sourceLocale: string;
|
|
33
|
+
sourceText: string;
|
|
34
|
+
}): TranslationRequest[];
|
|
35
|
+
/**
|
|
36
|
+
* Separate translations by store
|
|
37
|
+
*/
|
|
38
|
+
export declare function separateTranslationsByStore({ store, translations, app, sourceLocale, }: {
|
|
39
|
+
store: StoreType;
|
|
40
|
+
translations: Record<string, string>;
|
|
41
|
+
app: RegisteredApp;
|
|
42
|
+
sourceLocale: string;
|
|
43
|
+
}): {
|
|
44
|
+
appStore: Record<string, string>;
|
|
45
|
+
googlePlay: Record<string, string>;
|
|
46
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release notes translation utilities.
|
|
3
|
+
* Prepares translation requests per store and gathers supported locales from registered apps.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Collect supported locales from registered app info
|
|
7
|
+
*/
|
|
8
|
+
export function collectSupportedLocales({ app, store, }) {
|
|
9
|
+
const appStoreLocales = [];
|
|
10
|
+
const googlePlayLocales = [];
|
|
11
|
+
if ((store === "both" || store === "appStore") &&
|
|
12
|
+
app.appStore?.supportedLocales) {
|
|
13
|
+
appStoreLocales.push(...app.appStore.supportedLocales);
|
|
14
|
+
}
|
|
15
|
+
if ((store === "both" || store === "googlePlay") &&
|
|
16
|
+
app.googlePlay?.supportedLocales) {
|
|
17
|
+
googlePlayLocales.push(...app.googlePlay.supportedLocales);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
appStore: appStoreLocales,
|
|
21
|
+
googlePlay: googlePlayLocales,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create translation requests per store
|
|
26
|
+
*/
|
|
27
|
+
export function createTranslationRequests({ store, targetLocales, sourceLocale, sourceText, }) {
|
|
28
|
+
const requests = [];
|
|
29
|
+
if (store === "both" || store === "appStore") {
|
|
30
|
+
if (targetLocales.appStore.length > 0) {
|
|
31
|
+
requests.push({
|
|
32
|
+
store: "appStore",
|
|
33
|
+
sourceText,
|
|
34
|
+
sourceLocale,
|
|
35
|
+
targetLocales: targetLocales.appStore,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (store === "both" || store === "googlePlay") {
|
|
40
|
+
if (targetLocales.googlePlay.length > 0) {
|
|
41
|
+
requests.push({
|
|
42
|
+
store: "googlePlay",
|
|
43
|
+
sourceText,
|
|
44
|
+
sourceLocale,
|
|
45
|
+
targetLocales: targetLocales.googlePlay,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return requests;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Separate translations by store
|
|
53
|
+
*/
|
|
54
|
+
export function separateTranslationsByStore({ store, translations, app, sourceLocale, }) {
|
|
55
|
+
const appStoreTranslations = {};
|
|
56
|
+
const googlePlayTranslations = {};
|
|
57
|
+
const appStoreLocales = app.appStore?.supportedLocales;
|
|
58
|
+
const googlePlayLocales = app.googlePlay?.supportedLocales;
|
|
59
|
+
for (const [locale, text] of Object.entries(translations)) {
|
|
60
|
+
if (store === "both" || store === "appStore") {
|
|
61
|
+
if (!appStoreLocales || appStoreLocales.includes(locale)) {
|
|
62
|
+
appStoreTranslations[locale] = text;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (store === "both" || store === "googlePlay") {
|
|
66
|
+
if (!googlePlayLocales || googlePlayLocales.includes(locale)) {
|
|
67
|
+
googlePlayTranslations[locale] = text;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Always include sourceLocale if provided and missing
|
|
72
|
+
if (store === "both" || store === "appStore") {
|
|
73
|
+
if (translations[sourceLocale] && !(sourceLocale in appStoreTranslations)) {
|
|
74
|
+
appStoreTranslations[sourceLocale] = translations[sourceLocale];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (store === "both" || store === "googlePlay") {
|
|
78
|
+
if (translations[sourceLocale] &&
|
|
79
|
+
!(sourceLocale in googlePlayTranslations)) {
|
|
80
|
+
googlePlayTranslations[sourceLocale] = translations[sourceLocale];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
appStore: appStoreTranslations,
|
|
85
|
+
googlePlay: googlePlayTranslations,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ResolvedAppContext, type ServiceResult } from "./types.js";
|
|
2
|
+
interface ResolveAppOptions {
|
|
3
|
+
slug?: string;
|
|
4
|
+
bundleId?: string;
|
|
5
|
+
packageName?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolve registered app and store identifiers from user input.
|
|
9
|
+
* Separates "which store data is available" from tool-level orchestration.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AppResolutionService {
|
|
12
|
+
resolve(options: ResolveAppOptions): ServiceResult<ResolvedAppContext>;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AppError } from "../../packages/common/errors/app-error.js";
|
|
2
|
+
import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
|
|
3
|
+
import { findApp } from "../../packages/configs/secrets-config/registered-apps.js";
|
|
4
|
+
import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
|
|
5
|
+
import { serviceFailure, serviceSuccess } from "./service-helpers.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve registered app and store identifiers from user input.
|
|
8
|
+
* Separates "which store data is available" from tool-level orchestration.
|
|
9
|
+
*/
|
|
10
|
+
export class AppResolutionService {
|
|
11
|
+
resolve(options) {
|
|
12
|
+
const { slug, bundleId, packageName } = options;
|
|
13
|
+
const identifier = slug || bundleId || packageName;
|
|
14
|
+
if (!identifier) {
|
|
15
|
+
return serviceFailure(AppError.validation(ERROR_CODES.APP_IDENTIFIER_MISSING, "❌ App not found. Please provide app (slug), packageName, or bundleId."));
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const app = findApp(identifier);
|
|
19
|
+
if (!app) {
|
|
20
|
+
return serviceFailure(AppError.notFound(ERROR_CODES.APP_NOT_FOUND, `❌ App registered with "${identifier}" not found. Check registered apps using apps-search.`));
|
|
21
|
+
}
|
|
22
|
+
return serviceSuccess({
|
|
23
|
+
app,
|
|
24
|
+
slug: app.slug,
|
|
25
|
+
bundleId: bundleId ?? app.appStore?.bundleId,
|
|
26
|
+
packageName: packageName ?? app.googlePlay?.packageName,
|
|
27
|
+
hasAppStore: Boolean(app.appStore),
|
|
28
|
+
hasGooglePlay: Boolean(app.googlePlay),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_RESOLUTION_FAILED, "Failed to resolve app"));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AppStoreReleaseNote } 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 { AppStoreClient } from "../../packages/stores/app-store/client.js";
|
|
5
|
+
import { type MaybeResult, type ServiceResult, type StoreAppSummary, type AppStoreVersionInfo, type UpdatedReleaseNotesResult, type PushAsoResult, type CreatedAppStoreVersion, type VerifyAuthResult } from "./types.js";
|
|
6
|
+
interface AppStoreAppInfo {
|
|
7
|
+
appId?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
supportedLocales?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* App Store-facing service layer that wraps client creation and common operations.
|
|
13
|
+
* Keeps MCP tools independent from client factories and SDK details.
|
|
14
|
+
*/
|
|
15
|
+
export declare class AppStoreService {
|
|
16
|
+
private getClientOrThrow;
|
|
17
|
+
createClient(bundleId: string): ServiceResult<AppStoreClient>;
|
|
18
|
+
/**
|
|
19
|
+
* List released apps. Uses a fresh client to ensure working directory independence.
|
|
20
|
+
*/
|
|
21
|
+
listReleasedApps(): Promise<ServiceResult<StoreAppSummary[]>>;
|
|
22
|
+
/**
|
|
23
|
+
* Fetch a single app info (with locales) by bundleId.
|
|
24
|
+
*/
|
|
25
|
+
fetchAppInfo(bundleId: string, existingClient?: AppStoreClient): Promise<MaybeResult<AppStoreAppInfo>>;
|
|
26
|
+
getLatestVersion(bundleId: string, existingClient?: AppStoreClient): Promise<MaybeResult<AppStoreVersionInfo>>;
|
|
27
|
+
updateReleaseNotes(bundleId: string, releaseNotes: Record<string, string>, versionId?: string, supportedLocales?: string[]): Promise<ServiceResult<UpdatedReleaseNotesResult>>;
|
|
28
|
+
pullReleaseNotes(bundleId: string): Promise<ServiceResult<AppStoreReleaseNote[]>>;
|
|
29
|
+
createVersion(bundleId: string, versionString: string, autoIncrement?: boolean): Promise<ServiceResult<CreatedAppStoreVersion>>;
|
|
30
|
+
pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, }: {
|
|
31
|
+
config: EnvConfig;
|
|
32
|
+
bundleId?: string;
|
|
33
|
+
localAsoData: PreparedAsoData;
|
|
34
|
+
appStoreDataPath: string;
|
|
35
|
+
}): Promise<PushAsoResult>;
|
|
36
|
+
verifyAuth(expirationSeconds?: number): Promise<VerifyAuthResult<{
|
|
37
|
+
header: Record<string, unknown>;
|
|
38
|
+
payload: Record<string, unknown>;
|
|
39
|
+
}>>;
|
|
40
|
+
}
|
|
41
|
+
export {};
|